I/O模型
一个输入操作一般包括两个阶段:等待数据、从内核和进程复制数据
1. 阻塞式I/O : 直到读写完成或发生错误才返回,如被信号打断
2. 非阻塞I/O : 无读写数据时直接返回错误,有数据时直接复制数据
3. I/O复用 : 阻塞在select或poll上,而不是阻塞在真正的I/O系统调用上
4. 信号驱动式I/O(SIGIO) : 让内核在描述符就绪时发送SIGIO信号通知我们
5. 异步I/O(Posix的aio_系列函数): 告知内核启动某个操作,并让内核在整个操作完成后通知我们。
与信号I/O的区别是:信号I/O是内核通知何时可以启动一个操作,异步I/O通知I/O操作何时完成。
select、poll和epoll
select
int select(int maxfdp1, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
select 缺陷
1. 最大并发数限制
select对打开的文件描述符是有限制的,由FD_SETSIZE(1024)设置, 如果要改变FD_SIZE的大小需要重新编译内核。
2.效率低
调用select时,需要将readfds, writefds, exceptfds从用户层拷贝到内核
select返回需要线性遍历套接字集合(readfds, writefds, exceptfds)
select返回后,
poll
epoll
1. Level_triggered(水平触发)
当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你.
只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket
若系统中有大量不需要读写的就绪文件描述符,它们每次都会返回,这样会降低处理程序检索自己关心的就绪文件描述符的效率!
2.Edge_triggered(边缘触发)
当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!
只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。
问题:accept 、ET、阻塞
1. 阻塞模式 accept 存在的问题
考虑这种情况:TCP连接被客户端夭折,即在服务器调用accept之前,客户端主动发送RST终止连接,导致刚刚建立的连接从就绪队列中移出,如果套接口被设置成阻塞模式,服务器就会一直阻塞在accept调用上,直到其他某个客户建立一个新的连接为止。但是在此期间,服务器单纯地阻塞在accept调用上,就绪队列中的其他描述符都得不到处理。
解决办法是把监听套接口设置为非阻塞,当客户在服务器调用accept之前中止某个连接时,accept调用可以立即返回-1,这时源自Berkeley的实现会在内核中处理该事件,并不会将该事件通知给epool,而其他实现把errno设置为ECONNABORTED或者EPROTO错误,我们应该忽略这两个错误。
2. ET模式下 accept存在的问题
考虑这种情况:多个连接同时到达,服务器的TCP就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll只会通知一次,accept只处理一个连接,导致TCP就绪队列中剩下的连接都得不到处理。
解决办法是用while循环抱住accept调用,处理完TCP就绪队列中的所有连接后再退出循环。如何知道是否处理完就绪队列中的所有连接呢?accept返回-1并且errno设置为EAGAIN就表示所有连接都处理完。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。