字符集网络通信模块源码

  

字符集用户手册:
系统的I/O可分为阻塞型,非阻塞同步型以及非阻塞异步型。
阻塞型I/O意味着控制权直到调用操作结束了才会回到调用者手里。结果调用者被阻塞了,这段时间了做不了任何其它事情。更郁闷的是,在等待IO结果的时间里,调用者所在线程此时无法腾出手来去响应其它的请求,这真是太浪费资源了。拿读()操作来说吧,调用此函数的代码会一直僵在此处直至它所读的套接字缓存中有数据到来。
相比之下,非阻塞同步是会立即返回控制权给调用者的。调用者不需要等,等它从调用的函数获取两种结果:要么此次调用成功进行了;要么系统返回错误标识告诉调用者当前资源不可用,你再等等或者再试度看吧。比如读()操作,如果当前插座无数据可读,则立即返回EWOULBLOCK/EAGAIN告诉调用读()者”数据还没准备好,你稍后再试”。
在非阻塞异步调用中,稍有不同。调用函数在立即返回时,还告诉调用者,这次请求已经开始了。系统会使用另外的资源或者线程来完成这次调用操作,并在完成的时候知会调用者(比如通过回调函数)。拿Windows的ReadFile()或者POSIX的aio_read()来说,调用它之后,函数立即返回,操作系统在后台同时开始读操作。
在以上三种IO形式中,理论上,非阻塞异步是性能最高,伸缩性最好的。
同步和异步是相对于应用和内核的交互方式而言的,同步需要主动去询问,而异步的时候内核在IO事件发生的时候通知应用程序,而阻塞和非阻塞仅仅是系统在调用系统调用的时候函数的实现方式而已。
对于JAVA的API来说:

  
      <李> java.net.Socket就是典型的阻塞型IO李   <李> java NIO非阻塞同步李   <李> java AIO非阻塞异步
    字符集起源于Cobar, Cobar前端为NIO后端为生物,后端就是通过java.net.Socket进行读写,所以Cobar后端每次进行读写都会造成线程阻塞,后端能支持的连接总数就成为瓶颈所在。
    字符集在基于Cobar改版时,直接采用了java 7的AIO,前后端都实现了非阻塞异步。由于Linux并没有真正实现AIO,实际测试下来,AIO并不比NIO快,反而性能上比NIO还要慢,所以字符集在2014年下半年,做了一次网络通信框架的大调整,改为同时支持AIO和NIO,通过启动参数让用户来选择哪种方式。虽然现在AIO比NIO慢,但是字符集仍然保留了AIO实现,就是为了Linux等真正实现AIO后,可以直接支持。
    反应堆和前摄器
    字符集同时实现了NIO和AIO,为了便于读者更清楚理解代码实现,先介绍NIO和AIO分布对应的两种设计模式:反应堆和前摄器
    一般情况下,I/O复用机制需要事件分享器(事件demultBossiplexor)。事件分享器的作用,即将那些读写事件源分发给各读写事件的处理者,就像送快递的在楼下喊:谁的什么东西送了,快来拿吧。开发人员在开始的时候需要在分享器那里注册感兴趣的事件,并提供相应的处理者(事件处理程序),或者是回调函数;事件分享器在适当的时候会将请求的事件分发给这些处理程序或者回调函数。
    涉及到事件分享器的两种模式称为:反应堆和前摄器。反应堆模式是基于同步I/O的,而前摄器模式是和异步I/O相关的。在反应堆模式中,事件分离者等待某个事件或者应用或操作的状态发生(比如文件描述符可读,写或者是插座可读写),事件分离者就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。
    而在前摄器模式中,事件处理者(或者代由事件分离者发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区,读的数据大小,或者用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分离者得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称有重叠的技术),事件分离者等IOCompletion事件完成。这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。
    反应堆与前摄器两种模式的场景区别:
    下面是反应堆的做法:   
        <李>等待事件响应(反应堆工作)   <李>分发“准备开始阅读”事件给用户句柄(反应堆工作)   <李>读数据(用户处理程序工作)   <李>处理数据(用户处理程序工作)
      下面再来看看真正意义的异步模式前摄器是如何做的:李

      字符集网络通信模块源码