最近看《Redis设计与实现》的文件事件,回顾了一下I/O多路复用的知识,又接着看了一下之前使用Java NIO的代码,突然有了一点新的理解,在此简单记录一下,但这里并不介绍I/O多路复用,NIO相关的基础知识。关于NIO的相关知识使用等,有需要可参考https://segmentfault.com/a/1190000006824196(以下代码也出自该博客,感谢博主)
下面这段代码是一个简单的Server端,可以接收Client端的连接请求,并建立一个与该Client的连接,从而实现Server与Client的数据传递。
public class EchoNIOServer {
private final static int PORT = 8088;
private final static int BUF_SIZE = 10240;
public static void main(String[] args) {
EchoNIOServer echoNIOServer = new EchoNIOServer();
echoNIOServer.initServer(); //Server初始化
}
private void initServer() {
try {
//S1创建通道管理器,Selector就是NIO提供的通道管理器的实现
Selector selector = Selector.open();
//创建一个通道对象,这里创建的是ServerSocketChannel的实例,ServerSockerChannel允许监听TCP连接请求
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
//将刚刚创建的ServerSocketChannel对象绑定到PORT端口上,说明这个ServerSocketChannel实例监听的是来自PORT端口的连接,Client需要将连接发送到PORT端口
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
//S2将创建的ServerSocketChannel通道实例注册到通道管理器上,并说明该通道实例监听的对象是OP_ACCEPT,告诉通道管理器,当我的通道上有OP_ACCEPT事件的时候要告诉我哦
SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//接下来Server的任务,就是不断查询当前是否有事件到达了
while (true) {
selector.select(); //S3如果通道管理器上的任何一个通道收到了感兴趣的事件,该函数返回,否则程序阻塞等待
Set<SelectionKey> keySet = selector.selectedKeys(); //这里说明通道管理器上的某个通道收到了感兴趣的事件了,也可能是多个通道都收到了,所以是一个Set
Iterator<SelectionKey> iterator = keySet.iterator();
//遍历收到的事件
while (iterator.hasNext()) {
SelectionKey key = (SelectionKey) iterator.next();
iterator.remove();
if (key.isAcceptable()) { //S4当前事件类型是OP_ACCEPT,调用doAccept函数
doAccept(key);
} else if (key.isReadable()) { //S6当前事件类型是OP_READ,调用doRead函数
doRead(key);
} else if (key.isWritable() && key.isValid()) {
doWrite(key);
}
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void doAccept(SelectionKey key) {
try {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
System.out.println("ServerSocketChannel正在循环监听");
//S5服务器通道管理器上注册一个与发送OP_ACCEPT的客户端管理的通道,此时客户端会收到一个OP_CONNECT事件
SocketChannel clientChannel = serverSocketChannel.accept();
clientChannel.configureBlocking(false);
clientChannel.register(key.selector(), SelectionKey.OP_READ);
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void doRead(SelectionKey key) {
try {
SocketChannel clientChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
//S7将客户端发送来的消息读到该客户端管理通道的buffer中
long readBytes = clientChannel.read(byteBuffer);
while (readBytes > 0) {
byteBuffer.flip();
byte[] data = byteBuffer.array();
String info = new String(data).trim();
System.out.println("从客户端发送过来的消息是:" + info);
byteBuffer.clear();
readBytes = clientChannel.read(byteBuffer);
}
String info = "客户端你好!!!";
byteBuffer.clear();
byteBuffer.put(info.getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer); //S8给客户端通道实例中写入数据,此时客户端的通道会收到OP_READ事件
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void doWrite(SelectionKey key) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
byteBuffer.flip();
SocketChannel clientChannel = (SocketChannel) key.channel();
while(byteBuffer.hasRemaining()) {
clientChannel.write(byteBuffer);
}
byteBuffer.compact();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
下面这段代码是一个简单的Client端
public class EchoNIOClient {
private final static int PORT = 8088;
private final static int BUF_SIZE = 10240;
public static void main(String[] args) {
EchoNIOClient echoNIOClient = new EchoNIOClient();
echoNIOClient.initClient(); //Client初始化
}
private void initClient() {
Selector selector = null;
try {
selector = Selector.open();
SocketChannel clientChannel = SocketChannel.open();
clientChannel.configureBlocking(false);
clientChannel.connect(new InetSocketAddress("localhost", PORT)); //C1连接到服务器的PORT端口,此时监听服务器PORT端口的ServerSocketChannel通道实例会收到一个OP_ACCEPT事件
clientChannel.register(selector, SelectionKey.OP_CONNECT);
while (true) {
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isConnectable()) { //C2当SocketChannel通道实例收到OP_CONNECT事件后,调用doConnect()处理
doConnect(key);
} else if(key.isReadable()) { //C4
doRead(key);
}
}
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void doConnect(SelectionKey key) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
SocketChannel clientChannel = (SocketChannel) key.channel();
if (clientChannel.isConnectionPending()) {
clientChannel.finishConnect();
}
clientChannel.configureBlocking(false);
String info = "服务器你好!!!";
byteBuffer.clear();
byteBuffer.put(info.getBytes("UTF-8"));
byteBuffer.flip();
clientChannel.write(byteBuffer); //C3向通道的buffer空间写内容,这个时候,服务器上与该客户端关联的通道会收到一个OP_READ事件
clientChannel.register(key.selector(), SelectionKey.OP_READ);
} catch (IOException ex) {
ex.printStackTrace();
}
}
private void doRead(SelectionKey key) {
try {
ByteBuffer byteBuffer = ByteBuffer.allocate(BUF_SIZE);
SocketChannel clientChannel = (SocketChannel) key.channel();
clientChannel.read(byteBuffer); //C5
byte[] data = byteBuffer.array();
String msg = new String(data).trim();
System.out.println("服务端发送消息:" + msg);
clientChannel.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
以上Server/Client的执行顺序如下: