晚上学习了下Java 的 NIO Socket编程,写了下面这个小程序,包括服务器端与客户端。实现的功能为客户端向服务器端发送随即数目的消息,服务器端一条一条的回应。消息内容保存在talks.properties文件中,内容为:
Hi=Hi
Bye=Bye
床前明月光=疑是地上霜
举头望明月=低头思故乡
少小离家老大回=乡音无改鬓毛衰
天王盖地虎=宝塔镇河妖
我是甲=我是乙
我是客户端=我是服务器
我是周星驰=我是周润发
客户端会随即发送“=”左边的消息,服务器端会回应客户端“=”右边的消息。如果客户端想断开连接,会向服务器发送一个"Bye",然后服务器会回应一个"Bye“。收到服务器端的"Bye"后,客户端会断开连接。
当然,java的properties文件不接受中文内容,你需要native2ascii一下。talks.properties 的实际文件内容为:
Hi=Hi
Bye=Bye
\u5E8A\u524D\u660E\u6708\u5149=\u7591\u662F\u5730\u4E0A\u971C
\u4E3E\u5934\u671B\u660E\u6708=\u4F4E\u5934\u601D\u6545\u4E61
\u5C11\u5C0F\u79BB\u5BB6\u8001\u5927\u56DE=\u4E61\u97F3\u65E0\u6539\u9B13\u6BDB\u8870
\u5929\u738B\u76D6\u5730\u864E=\u5B9D\u5854\u9547\u6CB3\u5996
\u6211\u662F\u7532=\u6211\u662F\u4E59
\u6211\u662F\u5BA2\u6237\u7AEF=\u6211\u662F\u670D\u52A1\u5668
\u6211\u662F\u5468\u661F\u9A70=\u6211\u662F\u5468\u6DA6\u53D1
看下服务器端的代码。此例中的服务器端只有一个主线程,用于selector操作,并处理多个客户端的消息。在常规的socket编程中,每个客户端都需要单独开一个线程,效率比较低。代码为:
package helloweenpad;
import java.io.FileInputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.Iterator;
import java.util.Properties;
public class MyFirstNIOServer {
public static final int PORT = 12315;
protected Selector selector;
protected Charset charset = Charset.forName("UTF-8");
protected CharsetEncoder charsetEncoder = charset.newEncoder();
protected CharsetDecoder charsetDecoder = charset.newDecoder();
protected Properties talks = new Properties();
int clientCount;
public MyFirstNIOServer() throws Exception {
talks.load(new FileInputStream("E:\\talk.properties"));
selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(PORT)); // port
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);// register
p("Server localhost:" + PORT + " started. waiting for clients. ");
while (true) {
// selector 线程。select() 会阻塞,直到有客户端连接,或者有消息读入
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove(); // 删除此消息
// 并在当前线程内处理。(为了高效,一般会在另一个线程中处理此消息,例如使用线程池等)
handleSelectionKey(selectionKey);
}
}
}
public void handleSelectionKey(SelectionKey selectionKey) throws Exception {
if (selectionKey.isAcceptable()) {
// 有客户端进来
clientCount++;
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
Socket socket = socketChannel.socket();
// 立即注册一个 OP_READ 的SelectionKey, 接收客户端的消息
SelectionKey key = socketChannel.register(selector, SelectionKey.OP_READ);
key.attach("第 " + clientCount + " 个客户端 [" + socket.getRemoteSocketAddress() + "]: ");
p(key.attachment() + "\t[connected] =========================================");
} else if (selectionKey.isReadable()) {
// 有消息进来
ByteBuffer byteBuffer = ByteBuffer.allocate(100);
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
try {
int len = socketChannel.read(byteBuffer);
// 如果len>0,表示有输入。如果len==0, 表示输入结束。需要关闭 socketChannel
if (len > 0) {
byteBuffer.flip();
String msg = charsetDecoder.decode(byteBuffer).toString();
// 根据客户端的消息,查找到对应的输出
String newMsg = talks.getProperty(msg);
if (newMsg == null)
newMsg = "Sorry? I don't understand your message. ";
// UTF-8 格式输出到客户端,并输出一个'n'
socketChannel.write(charsetEncoder.encode(CharBuffer.wrap(newMsg + "\n")));
p(selectionKey.attachment() + "\t[recieved]: " + msg + " ----->\t[send]: " + newMsg);
} else {
// 输入结束,关闭 socketChannel
p(selectionKey.attachment() + "read finished. close socketChannel. ");
socketChannel.close();
}
} catch (Exception e) {
// 如果read抛出异常,表示连接异常中断,需要关闭 socketChannel
e.printStackTrace();
p(selectionKey.attachment() + "socket closed? ");
socketChannel.close();
}
} else if (selectionKey.isWritable()) {
p(selectionKey.attachment() + "TODO: isWritable() ???????????????????????????? ");
} else if (selectionKey.isConnectable()) {
p(selectionKey.attachment() + "TODO: isConnectable() ????????????????????????? ");
} else {
p(selectionKey.attachment() + "TODO: else. ");
}
}
public static void p(Object object) {
System.out.println(object);
}
public static void main(String[] args) throws Exception {
new MyFirstNIOServer();
}
}
再看下客户端代码。这个客户端使用了常规的socket编程,没有使用NIO。是否使用NIO对另一方是透明的,对方看不见,也不关心。无论使用NIO还是使用常规socket,效果都是一样的,只是NIO的效率要高一些。代码为:
package helloweenpad;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Properties;
import java.util.Random;
public class MyFirstNIOClientTest extends Thread {
public static final String HOST = "localhost";
public static final int PORT = 12315;
boolean exist = false;
Properties talks = new Properties();
Random random = new Random();
String[] keys;
int messageCount = 0;
public void run() {
try {
// 对话内容
talks.load(new FileInputStream("E:\\talk.properties"));
// 客户端发送 "=" 左边的内容
keys = new String[talks.size()];
talks.keySet().toArray(keys);
Socket socket = new Socket(HOST, PORT);
OutputStream ous = socket.getOutputStream();
InputStream ins = socket.getInputStream();
// 接收线程,接收服务器的回应
RecieverThread reciever = new RecieverThread(ins);
reciever.start();
while (!exist) {
messageCount++;
// 选择一个随机消息
String msg = chooseMessage();
synchronized (ins) {
// 发送给服务器端
ous.write(msg.getBytes("UTF-8"));
System.out.println("[send]\t" + messageCount + ": " + msg);
// 然后等待接收线程
ins.wait();
}
if (msg.equals("Bye")) {
break;
}
}
ins.close();
ous.close();
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public String chooseMessage() {
int index = random.nextInt(keys.length);
String msg = keys[index];
// 如果 10 次就选中 Bye,则重新选择,为了使对话内容多一些
if (messageCount < 10 && msg.equalsIgnoreCase("Bye")) {
return chooseMessage();
}
return msg;
}
// 接收线程
class RecieverThread extends Thread {
private InputStream ins;
public RecieverThread(InputStream ins) {
this.ins = ins;
}
@Override
public void run() {
try {
String line = null;
BufferedReader r = new BufferedReader(new InputStreamReader(
ins, "UTF-8"));
// readLine()会阻塞,直到服务器输出一个 '\n'
while ((line = r.readLine()) != null) {
System.out.println("[Recieved]: " + line);
synchronized (ins) {
// 接收到服务器的消息,通知下主线程
ins.notify();
}
if (line.trim().equals("Bye")) {
exist = true;
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
// 开三个客户端线程
for (int i = 0; i < 3; i++) {
try {
new MyFirstNIOClientTest().start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
=============================================================================
服务器端的输出:
Server localhost:12315 started. waiting for clients.
第 1 个客户端 [/127.0.0.1:1890]: [connected] =========================================
第 2 个客户端 [/127.0.0.1:1865]: [connected] =========================================
第 3 个客户端 [/127.0.0.1:1866]: [connected] =========================================
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是周星驰 -----> [send]: 我是周润发
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 少小离家老大回 -----> [send]: 乡音无改鬓毛衰
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是客户端 -----> [send]: 我是服务器
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是甲 -----> [send]: 我是乙
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是周星驰 -----> [send]: 我是周润发
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 举头望明月 -----> [send]: 低头思故乡
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 少小离家老大回 -----> [send]: 乡音无改鬓毛衰
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是甲 -----> [send]: 我是乙
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 床前明月光 -----> [send]: 疑是地上霜
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是客户端 -----> [send]: 我是服务器
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Hi -----> [send]: Hi
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: Hi -----> [send]: Hi
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: 我是周星驰 -----> [send]: 我是周润发
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: 我是周星驰 -----> [send]: 我是周润发
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 举头望明月 -----> [send]: 低头思故乡
第 2 个客户端 [/127.0.0.1:1865]: [recieved]: Bye -----> [send]: Bye
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: Hi -----> [send]: Hi
第 2 个客户端 [/127.0.0.1:1865]: read finished. close socketChannel.
第 1 个客户端 [/127.0.0.1:1890]: [recieved]: Bye -----> [send]: Bye
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是客户端 -----> [send]: 我是服务器
第 1 个客户端 [/127.0.0.1:1890]: read finished. close socketChannel.
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: 我是客户端 -----> [send]: 我是服务器
第 3 个客户端 [/127.0.0.1:1866]: [recieved]: Bye -----> [send]: Bye
第 3 个客户端 [/127.0.0.1:1866]: read finished. close socketChannel.
客户端的输出:
[send] 1: 我是周星驰
[send] 1: 少小离家老大回
[Recieved]: 乡音无改鬓毛衰
[send] 2: 床前明月光
[Recieved]: 疑是地上霜
[send] 3: 我是客户端
[Recieved]: 我是服务器
[Recieved]: 疑是地上霜
[Recieved]: 我是周润发
[Recieved]: 低头思故乡
[send] 2: 举头望明月
[Recieved]: 疑是地上霜
[send] 3: 床前明月光
[send] 1: 床前明月光
[send] 2: 举头望明月
[Recieved]: 低头思故乡
[Recieved]: 我是乙
[send] 4: 我是甲
[Recieved]: 我是周润发
[send] 5: 我是周星驰
[send] 3: 床前明月光
[send] 6: 床前明月光
[send] 4: 举头望明月
[Recieved]: 低头思故乡
[send] 5: 床前明月光
[Recieved]: 疑是地上霜
[Recieved]: 疑是地上霜
[Recieved]: 疑是地上霜
[Recieved]: Hi
[send] 4: Hi
[Recieved]: 低头思故乡
[send] 5: 举头望明月
[Recieved]: 低头思故乡
[Recieved]: 低头思故乡
[send] 6: 举头望明月
[send] 7: 少小离家老大回
[s
分享到:
相关推荐
java nio 编程一个实例子.服务端程序
主要讲解Java Socket编程中NIO TCP的实例,希望能给大家做一个参考。
主要讲解Java Socket编程中NIO UDP的实例,希望能给大家做一个参考。
[第4节] JavaNIO流-通道1.flv [第5节] Java NIO流-通道2.flv [第6节] Java NIO流-socket通道操作.flv [第7节] Java NIO流-文件通道操作.flv [第8节] Java NIO流-选择器 .flv [第9节] Java NIO流-选择器操作.flv...
javanio.zip 23.NIO非阻塞通信(Socket/UDP实例、简单聊天系统) javarmi.zip 24.RMI编程(HelloWorld例、计算器实例) javacorba.zip 25.Corba编程(HelloWorld例、计算器实例) 第6部分(4个程序包) java...
mina简单示例,Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP、UDP/IP协议栈的通信...Mina 提供了事件驱动、异步(Mina 的异步IO 默认使用的是JAVA NIO 作为底层支持)操作的编程模型。
javanio.zip 23.NIO非阻塞通信(Socket/UDP实例、简单聊天系统) javarmi.zip 24.RMI编程(HelloWorld例、计算器实例) javacorba.zip 25.Corba编程(HelloWorld例、计算器实例) 第6部分(4个程序包) java...
javanio.zip 23.NIO非阻塞通信(Socket/UDP实例、简单聊天系统) javarmi.zip 24.RMI编程(HelloWorld例、计算器实例) javacorba.zip 25.Corba编程(HelloWorld例、计算器实例) 第6部分(4个程序包) java...
一个很经典的基于NIO的java服务端与客户端实例
public void newConnection(NIOSocket nioSocket) { // 可在此处添加处理事件 } public void serverSocketDied(Exception exception) { // 关闭serverSocket处理 } }...
Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲区、写入数据到文件、关闭输入流、关闭套接字关闭输出流、输出错误信息等Java编程小技巧。 Java数组倒置...
Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲区、写入数据到文件、关闭输入流、关闭套接字关闭输出流、输出错误信息等Java编程小技巧。 Java数组倒置...
39_NIO中Scattering与Gathering深度解析 40_Selector源码深入分析 41_NIO网络访问模式分析 42_NIO网络编程实例剖析 43_NIO网络编程深度解析 44_NIO网络客户端编写详解 45_深入探索Java字符集编解码 46_字符集编解码...
42_NIO网络编程实例剖析 43_NIO网络编程深度解析 44_NIO网络客户端编写详解 45_深入探索Java字符集编解码 46_字符集编解码全方位解析 47_Netty服务器与客户端编码模式回顾及源码分析准备 48_Netty与NIO系统总结及NIO...
第42讲:NIO网络编程实例剖析 第43讲:NIO网络编程深度解析 第44讲:NIO网络客户端编写详解 第45讲:深入探索Java字符集编解码 第46讲:字符集编解码全方位解析 第47讲:Netty服务器与客户端编码模式回顾及源码...
11.1.3 使用UDP协议的Socket 网络编程 289 11.1.4 多点传送和MulticastSocket类 290 11.1.5 NIO及相关技术 290 11.2 练习 291 11.2.1 ServerSocket与Socket示例: 开发一个Server-Client模型的程序 291 11.2.2 ...
学生提问:老师,我想学习Java编程,到底是学习Eclipse好呢,还是学习JBuilder好呢? 21 1.9 本章小结 22 本章练习 22 第2章 理解面向对象 23 2.1 面向对象 24 2.1.1 结构化程序设计简介 24 2.1.2 程序的三种...