常见网络IO模型原理 & JAVA NIO模型

2018年11月7日

网络IO传输模式和编解码方案对系统的性能影响至关重要, 作为HTTP Server, 为什么Nginx 的网络IO性能很高, 而Tomcat 之类的Web Server 网络IO 性能相对较低 ?

系统选择的网络IO模型不同, Nginx使用的poll/epoll属于 多路复用型网络模型;

Tomcat 6 之前的版本都是用的阻塞式IO模型 (6版本之后支持 NIO模式了,网络IO有所提升) , 所以导致网络IO差距比较大

 

1. 都有哪些常见的网络IO类型可以选择? 各有什么特点?

*  blocking IO :

特点是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了, 性能比较低下; 


* nonblocking IO (NIO):

用户进程其实是需要不断的主动询问kernel数据准备好了没有,性能相对阻塞IO较高;

non-blocking IO在执行recvfrom这个系统调用的时候,如果kernel的数据没有准备好,这时候不会block进程。

但是当kernel中数据准备好的时候,recvfrom会将数据从kernel拷贝到用户内存中,这个时候进程是被block了,在这段时间内进程是被block的

“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用IO接口带来的资源占用。

而且,所谓“池”终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。

所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小

 

* IO multiplexing

本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程;

在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,整个用户的process其实是一直被block的

 

* asynchronous IO

异步IO是真正非阻塞的,它不会对请求进程产生任何的阻塞

用户进程发起read操作之后,立刻就可以开始去做其它的事。从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了

 

总结一下以上几种模型的特征:

 

 

 

 

2. Java NIO模型的实现

目前有很多JAVA 开源软件使用Java NIO 模型(包括Netty), 比如 新版本的Tomcat, MapReduce, RocketMQ等

JAVA NIO中有几个比较关键的概念:Channel(通道),Buffer(缓冲区),Selector(选择器):

     

 

Channel:

Channel 用于在字节缓冲区和位于通道另一侧的实体(通常是一个文件或套接字)之间有效地传输数据;

Channel和传统IO中的Stream很相似。但是有很大的区别,主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写;

以下是常用的几种通道:

  • FileChannel—用于从文件读或者向文件写入数据
  • SocketChanel —-用于以TCP来向网络连接的两端读写数据
  • ServerSocketChannel —-用于服务端, 监听客户端发起的TCP连接,并为每个TCP连接创建一个新的SocketChannel来进行数据读写
  • DatagramChannel—-用于以UDP协议来向网络连接的两端读写数据

Buffer(缓冲区):

缓冲区,实际上是一个容器,是一个连续数组。Channel提供从文件、网络读取数据的Channel,但是应用程序与 file/socket 之间的读取或写入的数据都必须经由Buffer

在NIO中,读取的数据只能放在Buffer中。同样地,写入数据也是先写入到Buffer中。

在NIO中,Buffer是一个顶层父类,它是一个抽象类,常用的Buffer的子类有:

  • ByteBuffer
  • IntBuffer
  • CharBuffer
  • LongBuffer
  • DoubleBuffer
  • FloatBuffer
  • ShortBuffer

对于文件读写,上面几种Buffer都可能会用到

对于网络读写来说,用的最多的是ByteBuffer

 

Selector:

Selector相当于一个观察者,作用就是用来轮询每个注册的Channel,一旦发现Channel有注册的事件发生,便获取事件然后进行处理。一个选择器可以绑定多个通道.

通道向选择器注册时,需要指定感兴趣的事件,选择器支持以下事件:

    • SelectionKey.OP_CONNECT
    • SelectionKey.OP_ACCEPT
    • SelectionKey.OP_READ
    • SelectionKey.OP_WRITE

如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE; 

通道向选择器注册时,会返回一个 SelectionKey对象,具有如下属性

    • interest集合
    • ready集合
    • Channel
    • Selector
    • 附加的对象(可选)

 

 

 

 

没有评论

发表评论

邮箱地址不会被公开。 必填项已用*标注