很幽默的讲解六种Socket IO模型 看了还是还是有些模糊,libevent的A tiny introduction to asynchronous IO 更清晰。
可以仿照前面那篇写一下,建立的模型更健全点。
假设老陈是服务器端应用程序。
老陈的多个女儿是客户端应用程序。
送信的邮递员是网络。
操作系统式是个信箱管理员。邮递员不能直接投到信箱,是先把信给信箱管理员,信箱管理员再把信放到指定的信箱。
信箱是已经和客户端相连的socket。listen的socket想不到好的比喻。
装信的袋子是buffer。每次老陈都是拿一袋子的。
刚开始女儿没给老陈寄信,信箱管理员对老陈说有女儿可能要开始给你寄信了,老陈就去挂一个信箱(accept)。
传统的单线程阻塞同步
老陈站在信箱A旁,等信箱管理员把信放入信箱A时,再一封封放入袋子,然后拿走。
再到下一个信箱B等着。
如果信箱管理员把信先放到B了而没放入A,老陈也还是在信箱A等着,不会先去看B。
多线程阻塞同步
老陈有好几个老婆。多一个女儿开始准备寄信,老陈就多挂一个信箱。同时老陈自己是不看信箱的,会叫来一个老婆看着守信。
这样效率比单线程阻塞高,但叫老婆要开销,而且老婆可能不够女儿多。
单线程非阻塞同步
老陈不停轮询检查每个邮箱是否有新的信,有就装到袋子里然后拿去处理。没有就马上跳过,而不是像阻塞那样,一直待在那个信箱等到有信为止。
处理完一个邮箱再到下一个邮箱。
Select同步(阻塞的)
一直不停检查邮箱老陈要累死了,老陈发现其实信箱管理员是知道什么时候来信的,于是和信箱管理员商量了一个计划。
老陈一直在信箱旁很清闲得等着(阻塞,但是这个阻塞的线程就没循环检查的CPU开销了),信箱管理员从邮递员那里拿到老陈的信时就通知老陈有信来了。
老陈再去轮询检查一遍所有信箱是否有新的信来了,哪个信箱有信就一封封放到袋子里拿走。
select一个线程最多管理64个socket,要实现多于64个必须通过多个线程。
WSAAsyncSelect异步
老陈本来是一直在信箱旁很清闲得等着,现在老陈可以去做其他事了,信箱管理员在有信的时候把老陈叫回来收信。
通过窗口消息循环实现异步,跟IOCP类似,但是邮箱管理员不会自己把信放袋子里,还是在用户代码中同步recv send。
因为是基于select实现,一个线程也只能管理64个socket。
WSAEventSelect异步
跟WSAAsyncSelect类似,但是不是通过消息实现,而是通过事件对象。
因为是基于select实现,一个线程也只能管理64个socket。
Epoll同步(阻塞的)
epoll老陈发现其实信箱管理员除了知道什么时候来信,还知道哪个信箱来信了。
老陈一直在邮箱旁很清闲得等着,信箱管理员从邮递员那里拿到老陈的信时就通知老陈有信来了,同时还告诉老陈是哪个信箱有信或可以发信(即哪个邮箱可进行什么操作都具体告诉了老陈)。
老陈不用检查每个邮箱了,只要对有效的邮箱进行相应的操作就行了。
无64个socket限制。
IOCP异步
老陈发现自己把信一封封放袋子很烦,于是把袋子(读的话空袋子,写的话装满信的袋子)也给了信箱管理员,同时告诉管理员要对袋子里的信进行什么操作。
如果是读操作信箱管理员把信都放到袋子后,再执行老陈之前所告诉他的操作就好了,没老陈啥事儿了。
无64个socket限制。
网络框架
linux的epoll没有windows的iocp,而且除了这两个其他系统用的高效网络实现也都不一样,比如BSD的kqueue,solaris的dev/poll。
于是写跨系统的程序时需要用网络框架,如Libevent、boost.asio等。
大部分都是同步读写接口,就是管理员不会把信装袋子的;只有windows的是异步的。
于是这些框架的异步编程都是模拟的,通过框架模拟邮箱管理员把信放到一个袋子。
从IO - 同步,异步,阻塞,非阻塞 (亡羊补牢篇) Stevens在文章中一共比较了五种IO Model:
a.阻塞型 IO(blocking I/O) 只调用recv和send
b.非阻塞性IO(nonblocking I/O) 只调用recv和send
c.IO多路复用(I/O multiplexing) select, epoll后,调用调用recv和send
d.信号驱动IO(signal driven I/O) 有windows的WSAAsyncSelect和WSAEventSelect,等状态检查阶段变化信号触发后调用recv和send
e.异步IO(asynchronous I/O) 不用调用recv和send
recv和send这两个个接口,都需经过状态检查阶段和IO阶段。同步异步,阻塞非阻塞 也就体现在这两个阶段:
1.状态检查阶段:检查socket的状态是否改变为可读写等。(等待数据准备好,也就是从网络接收到数据放到内核缓冲区)
一、同步异步:abc都同步的,de是异步的。
二、阻塞非阻塞: ac是阻塞的,b是非阻塞的,de由于是异步的没有主动去检测(内核搞定)。
2.IO阶段:rece、send读写数据。(将数据从内核缓冲区复制到用户进程缓冲区)
一、同步异步:abcd都是同步的,只有e是异步的。
一、阻塞非阻塞:a是阻塞,b是非阻塞,cd因为已经经过状态检查阶段确认有数据的所以无所谓阻塞非阻塞,e因为IO是异步的没有主动去IO(内核搞定)。