Netty(一)网络IO基础

在讨论IO的时候,参与者通常有两个角色:系统内核和用户进程。用户进程发送 IO请求过后,系统内核在准备好IO数据后,会通过内存拷贝的方式,将准备好的缓存IO数据共享给用户进程缓存。

网络I/O模型简介

根据UNIX网络编程对I/O模型的分类,提供了阻塞I/O模型、非阻塞I/O模型、I/O复用模型、信号驱动I/O模型、异步I/O5I/O模型。

1、阻塞I/O模型

最常用的模型,所有文件操作都是阻塞的。套接字socket在进程空间中调用recvfrom,其系统调用直到数据包到达,且被复制到应用进程的缓冲区或者发生错误的时候才返回,在此期间一直会等待,进程从调用recvfrom开始到它返回的整段时间内都是被阻塞的,因此被称为阻塞I/O模型。常用IO模型交互图如图所示。

首先应用程序调用recvfrom()转入内核,注意内核有2个过程,等待数据就绪和拷贝内核数据到用户空间,直到最后复制完成后,recvfrom()才返回,此过程一直是阻塞的。

2、非阻塞I/O模型

recvfrom从应用层到内核的时候,如果该缓冲区没有数据的话,就直接返回一个EWOULDBLOCK错误,一般都对非阻塞I/O模型进行轮询检查这个状态,看内核是不是有数据到来,如图所示。

3I/O复用模型

Linux提供select/poll,进程通过将一个或者多个fd传递给select或者poll系统调用,阻塞在select操作上,这样select/poll可以帮我们侦测多个文件描述符fd是否处于就绪状态。select/poll是顺序扫描fd是否就绪,而且支持的fd数量有限,因此它的使用受到一些限制。Linux还提供了一个epoll系统调用,epoll基于事件驱动方式代替顺序扫描,性能更高,当有fd就绪时,立即回调函数rollback,如图所示。

select先阻塞,有活动套接字才返回。与blocking I/O相比,select会有两次系统调用,但是select能处理多个套接字。

4、信号驱动I/O模型

首先开启套接字信号驱动I/O功能,并通过系统调用sigaction执行一个信号处理函数(此系统调用立即返回,进程继续工作,它是非阻塞的)。当数据准备就绪时,就为该进程生成一个SIGIO信号,通过信号回调通知应用程序调用recvfrom来读取数据,并通知主循环函数处理数据,如图所示。

只有Unix系统支持,与I/O multiplexing (select and poll)相比,它的优势是,免去了select的阻塞与轮询,当有活跃套接字时,由注册的handler处理。

5、异步I/O

告知内核启动某个操作,并让内核在整个操作完成之后(包括将数据从内核复制到用户自己的缓冲区)通知我们。这种模型与信号驱动模型的主要区别就是:信号驱动I/O由内核通知我们何时可以开始一个I/O操作;异步I/O模型由内核通知我们I/O操作何时已经完成。

很少有Linux/Unix系统支持,WindowsIOCP则是此模型,完全异步的I/O复用机制,纵观上面其它四种模型,至少都会在由kernel copy data to appliction时阻塞。而该模型是当copy完成后才通知application,可见是纯异步的,好像只有Windows的完成端口是这个模型,效率也很出色。

I/O多路复用技术

I/O多路复用的最大优势是系统开销小,系统不需要创建新的额外进程或者线程,也不需要维护这些进程和线程的运行,降低了系统的维护工作量,节省了系统资源,主要应用场景如下:

1、服务器需要同时处理多个处于监听状态或者多个连接状态的套接字。

2、服务器需要同时处理多种网络协议的套接字。

目前支持I/O多路复用的系统调用有selectpselectpoll,epolllinux)、kqueueFreeBSD)、iocp(Windows),由于select/ poll的一些固有缺陷导致了它的应用受到了很大的限制,Linux中最终选择了epoll,来克服select的缺点。

select模型缺点
1
、最大并发数限制问题,select中一个进程所打开的FD(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024,对于需要支持上万个TCP连接的大型服务器来说显然太少了。

2I/O效率问题,select每次调用都会线性扫描全部的FD集合,效率就会呈现线性下降。

3、内存拷贝问题,select采取了内存拷贝方法,将内核中的FD消息通知给用户空间。

poll基本上效率和select是相同的,select缺点的23它都没有改掉。

epoll的改进和提升
1
、没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,1G内存的机器上大约有10万个句柄左右,具体数目可以cat /proc/sys/fs/file-max察看。

2I/O效率提升,不会随着FD数目的增加而线性下降,它只管你活跃的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于selectpoll
3
、使用mmap共享内存加速内核与用户空间的消息传递,内核和用户空间mmap共用一块内存来实现,省去了内存拷贝。

JavaI/O的演进

JDK1.0JDK1.3JavaI/O类库都非常原始,基于Java的所有Socket通信都采用了同步阻塞模式(BIO),在性能和可靠性方面存在着巨大的瓶颈。

2002JDK1.4发布,NIOJSR-51的身份随JDK发布,增加了java.nio包,提供了很多非阻塞I/O开发的类库,在JDK1.41.5 update 10版本之前,JDKselector基于select/poll模型,基于I/O复用的非阻塞I/O,不是异步I/O。在JDK1.5 update 10 Linux core2.6以上版本,优化了selector的实现,在底层使用epoll替换了select/poll,上层API没有变化,可认为是JDK NIO的一次性能优化。

2011JDK1.7发布,将原来的NIO进行了升级,通过JSR-203演进而来,称为NIO2.0,提供异步I/O开发类库。

1Java同步阻塞I/O

java中调用InputStream.read()或者OutputStream.write()时,用户进程会阻塞住直到数据就绪,相当于一个线程一个连接的方式。所以在采用Java IO时,在Server端通常会采用对于每个新连接,起一个新的线程去处理,这样后来的连接就不用等到之前的完成才能操作。但也带来了问题,毕竟线程是系统的稀缺资源,数量上会有瓶颈,达到一定数量后,性能急剧下降,内存崩溃。不能应对大量连接的情况,而且线程切换很耗费系统资源。

2Java NIO同步非阻塞IO

基于Java IO的缺点,NIO采用了新的设计方式,核心在ServerSocketChannel, SocketChannel,
FileChannel, ByteBuffer, Pipe, Selector
。非阻塞主要依靠SelectorChannelSelector上注册自己感兴趣的事件,然后Selector线程会轮询注册在自己身上的Channel,当有数据准备就绪时,就通知相应的Channel。这样一个Selector可以管理多个Channel,但实际上还是阻塞的,现在不阻塞IO层面了,阻塞在Selector线程上了。而且采用轮询的方式,效率比较低。

3Java AIO异步非阻塞IO

Java NIO的基础上,增加了AsynchronousServerSocketChannel, AsynchronousSocketChannel, AsynchronousChannelGroup,
CompletionHandler
,其中AsynchronousChannelGroup起到了事件收集和任务分发的作用,而CompletionHandler是绑定在事件上回调机制,从而达到异步。能否真正实现异步,关键还要看系统底层的实现,当前来看只有windowiocp实现了真正的异步,linux上还是通过epoll来模拟,是一种伪异步。

是否异步主要在系统内核数据拷贝到用户进程这个步骤来区分,同步的话是通知用户进程数据准备好了,可以拷贝了,然后用户进程阻塞去拷贝数据;异步的话是操作系统帮你把数据拷贝后,然后通知你数据好了,可以直接用了。