之前使用传统IO写网络编程的时候,一般都是想这样开个主线程这样写服务器端:
while(true){
ServerSocket serverSocket=new ServerSocket(8080);
Socket socket=serverSocket.accept();
/*生成子线程处理新socket*/
}
这样的话每有一个新请求的连接,我们都要新建一个线程去处理该连接,而且当该连接长时间无响应的时候,该线程也需要一直等待,十分浪费资源。为了解决这个问题,jdk为我们提供了基于通道的非阻塞的网络通信方式:ServerSocketChannel和SocketChannel.
基于通道的服务器端一般是这样写的:
Selector selector= Selector.open();
ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(8080));
serverSocketChannel.configureBlocking(false);//设置成非阻塞式状态
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//向选择器注册监听连接功能
while(true){
if (selector.select()>0){
Iterator<SelectionKey> iterator=selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey selectionKey= iterator.next();
if (selectionKey.isAcceptable()){
//如果有新的连接
SocketChannel socketChannel=((ServerSocketChannel)selectionKey.channel()).accept();
socketChannel.register(selector,SelectionKey.OP_READ);
}
if (selectionKey.isReadable()){
//如果有通道可读
}
if(selectionKey.isWriteable()){
//如果有通道可写
}
}
}
}
其中Selector顾名思义就是一个选择器,我们的服务端套接字通道和客户端套接字通道都要向其注册,表示我们要接受哪些事件的通知,比如 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
就代表了如果有新的连接进来就要通知我。如何拿到这个通知呢?通过selector的select()方法,我们可以拿到通知的所有事件,该方法是阻塞式的,意思是如果没有新的通知的话就一直等待。当有新的事件通知时,我们就可以根据事件的类型去处理请求了。有一点需要注意,虽然我们不需要再为每一个socket单独开一个线程了,但是由于处理事件时可能耗时很大(如上传电影),所以我们也需要启动单独的线程去响应事件,也就是说,线程不再服务于socket,而是服务于事件(选择性的),只要事件一结束,线程就停止了。
客户端的代码就没什么好说的了:
Selector selector=Selector.open();
SocketChannel socketChannel=SocketChannel.open(new InetSocketAddress("172.0.0.1",8080));
socketChannel.register(selector,SelectionKey.OP_READ);
这些基本上就是 NIO 的网络通信原型了。