网络IO模型BIO vs NIO
Posted 绝世好阿狸
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络IO模型BIO vs NIO相关的知识,希望对你有一定的参考价值。
先回顾下几个概念。
阻塞 vs 非阻塞:指的是调用者线程会不会被阻塞。BIO就是一种阻塞io,如果没有就绪,调用者线程会被阻塞挂起。而NIO是非阻塞的。
同步 vs 异步:指的是调用方式,同步调用可以理解为调用结果需要调用者主动获取,而异步调用则是使用回调/通知机制,将结果回调给调用者,调用者无需关心。
可以看到,二者是两个维度的概念,没有绑定关系。同步调用可以是阻塞的也可以是非阻塞的,比如BIO和NIO。异步调用通常是非阻塞的,比如AIO。
目前现状是BIO和NIO占绝大多数的场景,AIO却用的不是很多。
下面以Java程序为例,探讨下BIO和NIO各自的使用及优势。
1.BIO
在BIO下,所有的调用都是阻塞的,包括建连以及读写。
简单的单线程Server
public static void main(String[] args)
serverLoop();
private static void serverLoop()
byte[] buffer = new byte[4096];
try
ServerSocket serverSocket = new ServerSocket(15001);
while (true)
Socket socket = serverSocket.accept();
System.out.println("client connect");
InputStream inputStream = socket.getInputStream();
int size = inputStream.read(buffer);
String msg = new String(ArrayUtils.subarray(buffer, 0, size));
System.out.printf("receive data: %s, size: %d \\n", msg, size);
catch (IOException e)
e.printStackTrace();
功能很简单,接收客户端的链接,并将消息内容打印出来。
简单的客户端
public static void main(String[] args)
for (int i = 0; i < 2; i ++)
int finalI = i;
new Thread(() ->
try
Socket socket = new Socket("127.0.0.1", 15001);
OutputStream outputStream = socket.getOutputStream();
String msg = "hello" + finalI;
firstReqSleep(finalI);
outputStream.write(msg.getBytes());
outputStream.flush();
outputStream.close();
catch (IOException e)
e.printStackTrace();
).start();
try
Thread.sleep(100);
catch (InterruptedException e)
e.printStackTrace();
// 第二个请求不阻塞
private static void firstReqSleep(int i)
if (i == 1)
return;
try
Thread.sleep(5000);
catch (InterruptedException e)
e.printStackTrace();
进行了两次tcp数据发送,第一次sleep5秒,第二次直接发送。结果如下:
client connect
receive data: hello0, size: 6
client connect
receive data: hello1, size: 6
可以看到,单线程BIO server,只能串行处理请求,如果当前socket比较耗时,后面的socket只能等待,无并发能力。为了解决这个问题,可以采用多线程方式,多线程server如下。
public static void main(String[] args)
serverLoopMulti();
private static void serverLoopMulti()
byte[] buffer = new byte[4096];
try
ServerSocket serverSocket = new ServerSocket(15001);
while (true)
Socket socket = serverSocket.accept();
System.out.println("client connect");
new Thread(() ->
try
InputStream inputStream = socket.getInputStream();
int size = inputStream.read(buffer);
String msg = new String(ArrayUtils.subarray(buffer, 0, size));
System.out.printf("receive data: %s, size: %d \\n", msg, size);
catch (IOException e)
e.printStackTrace();
).start();
catch (IOException e)
e.printStackTrace();
client connect
client connect
receive data: hello1, size: 6
receive data: hello0, size: 6
可以看到,第一次请求sleep时,第二次请求被优先处理,并不受前面socket的影响。可以通过多线程方式提升BIO server的并发性能。这也是所谓的“request per thread”模型,即一个请求对应一个线程。这种模式的缺点也很明显,当并发数升高时,需要消耗大量的线程资源,线程数太高,也会一定程度上降低系统性能。优点也显而易见,编程简单。
2.NIO
因为BIO是阻塞IO,一个线程只能处理一个连接,所以诞生了NIO。NIO需要操作系统的支持,在linux操作系统上,是由select、poll以及epoll三类非阻塞io的系统调用来支持的,其中epoll效率最高。非阻塞IO的核心是多路复用技术,多路复用使得应用程序可以同时处理多个连接,并发性能得到很好的提升。那么多路复用技术相比于之前的多线程BIO模型的优势是什么?首先,二者都能够并发处理多个连接。如果并发连接数不多,二者性能没有太大差别,反而BIO由于编程简单更占优势。如果并发连接数很大,BIO就需要开启对应数量的线程,系统性能将会降低。而NIO的线程数与连接数无关,所以性能不受连接数增加影响。另外,假设并发连接数很高,并且有些连接是空闲的,BIO server下,这些连接会占用线程池资源,如果超过线程池的处理能力,新的请求将会触发拒绝策略,导致无法提供服务。但是NIO server却不受影响,只是select空闲连接时不返回事件,后续新的请求到达时仍然能够被处理。(前提是连接数没有超过操作系统限制)。
总结:
1.并发连接数不大,都可以;
2.并发连接数很高时,即便是空闲连接也会消耗BIO线程资源,而NIO不会;另外随着连接数增加,BIO线程数也随之增加,性能会下降,而NIO的线程数与连接数无关。
接下来看一个NIO server的例子
public class Nioserver
private static ServerSocketChannel serverSocketChannel;
private static Selector selector;
public static void main(String[] args)
try
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(15001));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true)
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
while (it.hasNext())
//分发事件
dispatch((SelectionKey) (it.next()));
it.remove();
catch (IOException e)
e.printStackTrace();
private static void dispatch(SelectionKey key)
if (key.isAcceptable())
try
System.out.println("client connect");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
catch (IOException e)
e.printStackTrace();
else if (key.isReadable())
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
try
int size = channel.read(byteBuffer);
String msg = new String(byteBuffer.array()).trim();
System.out.printf("receive data: %s, size: %d \\n", msg, size);
channel.close();
catch (IOException e)
e.printStackTrace();
client connect
client connect
receive data: hello1, size: 6
receive data: hello0, size: 6
可以看到,单线程的NIO server就能并发处理多个连接。NIO server依赖selector组件,需要将连接注册到selector上,serversocket只能注册accept事件,accept到连接以后,可以在连接上注册读写事件。不同于BIO轮询连接,NIO轮询selector,通过selector取得事件和连接。
参考:https://www.jianshu.com/p/bffbc1911053
以上是关于网络IO模型BIO vs NIO的主要内容,如果未能解决你的问题,请参考以下文章