二:阻塞与非阻塞I/O
阻塞和非阻塞主要是指调用某个系统函数时,这个函数是否会导致进程进入阻塞状态而言的;
a)阻塞I/O
调用一个函数,这个函数就卡在在这里,整个程序流程不往下走了,该函数卡在这里等待一个事情发生,这种情况下,操作系统会将该线程或进程挂起,不会消耗 CPU 资源,只有这个事情发生了,这个函数才会往下走,继续执行。
这种函数,就认为是阻塞函数;accept();
这种阻塞,并不好,效率很低;一般我们不会用阻塞方式来写服务器程序,效率低;
b)非阻塞I/O:不会卡住,充分利用时间片,执行更高;
非阻塞模式的两个鲜明特点:
(1)不断的调用accept(),recvfrom()函数来检查有没有数据到来,如果没有,函数会返回一个特殊的错误标记来告诉你,这种标记可能是 EWULDBLOCK
, 也可能是EAGAIN
;如果数据没到来,那么这里有机会执行其他函数,但是也得不停的再次调用accept(),recvfrom()来检查数据是否到来,非常累;
(2)如果数据到来,那么就得卡在这里把数据从内核缓冲区复制到用户缓冲区,所以复制这个阶段是卡着完成的(同步完成);
三:同步与异步I/O: 这两个概念容易和 阻塞/非阻塞混淆;
a)异步I/O:调用一个异步I/O函数时,我门要给这个函数指定一个接收缓冲区,我还要给定一个回调函数;
调用完一个异步I/O函数后,该函数会立即返回。 其余判断交给操作系统,操作系统会判断数据是否到来,如果数据到来了,操作系统会把数据拷贝到你所提供的缓冲区里,然后调用你所指定的这个回调函数来通知你;
很容易区别非阻塞和异步I/O的差别:
(1)非阻塞I/O要不停的调用I/O函数来检查数据是否来,如果数据来了,就得卡在I/O函数这里把数据从内核缓冲区复制到用户缓冲区,然后这个函数才能返回;
(2)异步I/O根本不需要不停的调用I/O函数来检查数据是否到来,只需要调用一次,然后就可以干别的事情去了;
内核判断数据到来,拷贝数据到你提供的缓冲区,调用你的回调函数来通知你,你并没有被卡在那里的情况;
b)同步I/O
select/poll。epoll。
1)调用select()判断有没有数据,有数据,走下来,没数据卡在那里;
2)select()返回之后,用recvfrom()去取数据;当然取数据的时候也会卡那么一下(拷贝过程);
同步I/O感觉更麻烦,要调用两个函数才能把数据拿到手;
但是同步I/O和阻塞式I/O比,就是所谓的 I/O复用【用两个函数来收数据的优势】 能力;
(3.1)I / O复用
所谓I/O复用,就是多个socket【多个TCP连接】可以弄成一捆【一堆】,我可以用select这种同步I/O函数在这等数据;
select()的能力是等多条TCP连接上的任意一条有数据来;,然后哪条TCP有数据来,我再用具体的比如recvfrom()去收。
所以,这种调用一个函数能够判断一堆TCP连接是否来数据的这种能力,叫I/O复用,英文I/O multiplexing【I/O多路复用】
很多资料把 阻塞I/O,非阻塞I/O,同步I/O归结为一类 ,因为他们多多少少的都有阻塞的行为发生;
甚至有的资料直接就把 阻塞I/O,非阻塞I/O 都归结为同步I/O模型,这也是可以的】
而把异步I/O单独归结为一类,因为异步I/O是真正的没有阻塞行为发生的;