BIO
在介绍NIO之前,我们来了解一下BIO以及为什么会出现NIO。下面来见一段传统的BIO形式的代码。
Server
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(9876));
while(true) {
System.out.println("等待连接");
//阻塞接收
//程序释放CPU资源
Socket accept = server.accept(); //mark1
System.out.println("连接成功");
//读数据
byte[] buff = new byte[1024];
int pos = accept.getInputStream().read(buff); //mark2
String content = new String(buff);
System.out.println("读出数据:" + content);
//写数据
accept.getOutputStream().write("服务器返回数据".getBytes());
System.out.println("数据返回成功");
}
Client
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 9876));
Scanner sc = new Scanner(System.in);
System.out.println("请输入内容");
while(true) {
String next = sc.next();
socket.getOutputStream().write(next.getBytes());
}
代码很简单,非常常见的服务端客户端通信。那么在这段代码中,服务端有两个地方会阻塞,分别为mark1和mark2所标记的代码处。
运行Server,程序阻塞在mark1处等待客户连接,此时启动Client,Server端将运行至mark2处阻塞等待Client传输数据,Client传输数据后,Server结束一次while循环。这是我们最开始传统的运行方式,这样的形式我Server端同时只能处理一个Client的请求(因为mark1处被阻塞了),如果想处理多个在BIO的模式下只能开多线程处理。
BIO的模式下多线程能同时处理多个客户端请求吗,答案是肯定的。但是会有什么问题:
- 多线程上下文切换的消耗
- 服务器开线程数量的限制
很明显会带来上述几个问题,那么我们能不能用单线程来处理多个客户端呢?单线程同时处理多个客户端请求我们要处理什么问题?
NIO
能否以单线程处理多个客户端呢?答案是肯定的,我们只要处理两个问题,第一,将上述案例中的mark1变为非阻塞,第二,将mark2变为非阻塞形式,来看一段伪代码。
Set<Socket> set = new HashSet<>();
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(9876));
while(true) {
setServerSocketNoBlock(server); //假设存在使得ServerSocket变为非阻塞的方法
Socket accept = server.accept();
if accept == null { //handle1
//是否有客户端发送数据
if yes {
for set {
accept.read(buff);
}
}
//continue
} else { //handle2
setSocketNoBlock(accept); //假设存在使得Socket变为非阻塞的方法
set.add(accept);
for set {
accept.read(buff);
}
//continue
}
}
在上述代码中我们将原来的mark1和mark2都变为非阻塞的形式,这样回发生什么,我们分析一下。Server端进入while循环,此时没有客户端连接accept 为null,没有客户端发送数据,程序进入handle1后进入下一次循环。一旦有客户端连接上来,假设此时A客户端连接上来但是不发送数据,程序进入handle2处理后进入下一次循环,此时B客户端连接上来,程序进入handle2后进入下一次循环,此时A客户端发送了一条数据消息,程序进入handle1并且接收到了A客户端的消息之后进入下一次循环。这样的形式就实现了我们单线程来处理多个客户端的需求。
那么上述伪代码中的最重要的地方其实是两处非阻塞的代码,从JDK1.4开始,jdk官方给我们提供了ServerSocketChannel以及SocketChannel这两个类来代替我们原来的ServerSocket和Socket,查看其API可以发现configureBlocking函数,看其注释可知,这个方法就能实现非阻塞的模式,那么我们上述的伪代码即可实现。NIO大致原理就是这样,其中NIO还有几个比较重要的概念buff、channel和selector,我们后续博客会简单分析一下。
/**
* Adjusts this channel's blocking mode.
*
* <p> If the given blocking mode is different from the current blocking
* mode then this method invokes the {@link #implConfigureBlocking
* implConfigureBlocking} method, while holding the appropriate locks, in
* order to change the mode. </p>
*/
public final SelectableChannel configureBlocking(boolean block)
throws IOException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if (blocking == block)
return this;
if (block && haveValidKeys())
throw new IllegalBlockingModeException();
implConfigureBlocking(block);
blocking = block;
}
return this;
}