Nio
一、Nio服务端程序启动和运行的详细过程:
1、打开一个serverSocketChannel(相当于ServerSocket),并绑定监听地址,设置连接为非阻塞
servChannel = ServerSocketChannel.open();//建立通道
servChannel.socket().bind(new InetSocketAddress(port), 1024);//通道绑定端口
servChannel.configureBlocking(false);//设置通道非阻塞
2、创建多路复用器selector,将serverSocketChannel注册到selector监听accept事件
(其实就是把serverSocketChannel生成的文件描述符FD和selector绑定在一起以方便以后使用通道进行数据读写)
selector = Selector.open();//创建selector
servChannel.register(selector, SelectionKey.OP_ACCEPT);//通道和selector绑定,监听accept事件
3、selector每隔一秒无限轮询准备就绪的key(客户端准备就绪可以连接服务端就向selector注册一个准备就绪的key)
selector.select(1000);//selector的等待时间是1s
Set<SelectionKey> selectedKeys = selector.selectedKeys();//查询出就绪的key
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {//遍历所有的key
key = it.next();
it.remove();
try {
//处理新的接入请求
} catch (Exception e) {
//处理异常就要处理掉这个key并关闭通道,客户端重新连接时需要重新注册key
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
4、selector监听到有新连接请求,则要处理新的连接请求,设置连接为非阻塞,并把socketChannel向selector注册,监听读操作
if (key.isAcceptable()) {
//处理新的接入请求
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//把socketChannel向selector注册,监听读操作
sc.register(selector, SelectionKey.OP_READ);
}
5、selector监听到有数据可读,就从该key对应的socketChannel中读取数据
if (key.isReadable()) {
//读数据
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);//设置buffer的块大小
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];//处理读半包问题
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server receive order : "+ body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new java.util.Date(
System.currentTimeMillis()).toString() : "BAD ORDER";
doWrite(sc, currentTime);//服务端给客户端返回的结果
} else if (readBytes < 0){//readBytes=-1
//对端链路关闭
key.cancel();
sc.close();
}
}
6、服务端向客户端写回的结果
private void doWrite(SocketChannel channel, String response)
throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
二、Nio客户端程序启动和运行的详细过程:
1、和服务端类似客户端需要有selector多路复用器和socketChannel
selector = Selector.open();//创建selector
socketChannel = SocketChannel.open();//创建客户端的socketChannel
socketChannel.configureBlocking(false);//设置通道非阻塞
2、连接服务端,
// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress(host, port))) {
socketChannel.register(selector, SelectionKey.OP_READ);//连接成功,注册到selector监听读数据
doWrite(socketChannel);
} else
socketChannel.register(selector, SelectionKey.OP_CONNECT);//连接不成功,注册到selector监听连接
}
3、selector每隔一秒无限轮询准备就绪的key
selector.select(1000);
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectedKeys.iterator();
SelectionKey key = null;
while (it.hasNext()) {
key = it.next();
it.remove();
try {
//处理和服务端新建立的连接
} catch (Exception e) {
//处理异常就要处理掉这个key并关闭通道,客户端重新连接时需要重新注册key
if (key != null) {
key.cancel();
if (key.channel() != null)
key.channel().close();
}
}
}
4、判读key如果连接成功则注册读监听,如果可读就从Channel中读数据
if (key.isValid()) {
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc);
} else
System.exit(1);// 连接失败,进程退出
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Now is : " + body);
this.stop = true;
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
}
}
}
5、客户端向服务端发送数据
private void doWrite(SocketChannel sc) throws IOException {
byte[] req = "QUERY TIME ORDER".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if (!writeBuffer.hasRemaining())
System.out.println("Send order 2 server succeed.");
}
补充:
//多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭
selector.close();
---恢复内容结束---