该篇博客类似做笔记的行为,记录自己关于学习IO多路复用过程中的一些个人理解,不喜勿喷,谢谢!!~
epoll采用的模式为边沿触发非阻塞I/O
水平/边沿触发:指的是epoll_wait()的触发行为
阻塞/非阻塞I/O:指的是文件描述符的读写(read)行为
- 使用水平触发非阻塞I/O:如果客户端发送10字节,服务端每次只能读5字节,水平触发就会导致缓冲区不断堆积。如果客户端发送5000字节,这5000字节首部有一个50字节长度的标识,服务器每次读取50字节,可以通过判断标识来决定后面的要不要,如果是水平触发,后面虽然客户端不再继续发送数据,但是epoll_wait还是会被触发为文件描述符可读,每次都进行数据读取。如果使用边沿触发就不会出现这种情况了。
- 使用边沿触发阻塞I/O:假如设置的readn是要一次读取500字节,但是客户端只发送了200字节过来,此时read就会发生阻塞,直到客户端再次发送数据过来,也就是说需要epoll_wait再次调用成功返回监听的文件描述符可读。但是此时已经阻塞在了read函数,即使客户端发送了数据,也无法触发epoll_wait,而是一直处于阻塞状态(饥渴状态)。
Linux高性能服务器编程P157页原话是:每个使用ET模式的文件描述符都应该是非阻塞的。如果文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件而一直处于阻塞状态(饥渴状态)。
read函数返回值为0的两种情况:
- 客户端主动关闭了连接
- read读到了文件末尾
在使用epoll_wait的边沿触发非阻塞I/O模型设计程序时
一般代码如下:
while (1) {
res = epoll_wait(epfd, res_event, max_event, -1);
...
if (res_event[0].data.fd == connfd) {// 该文件描述符是可读的文件描述符
while ((n = read(confd, buf, MAXLINE / 2)) > 0) { // 直到读到文件末尾才会推出
write(STDOUT_FILENO, buf, n);
}
}
个人认为还需要先进行一个客户端主动断开的判断
改进代码如下:
while (1) {
res = epoll_wait(epfd, res_event, max_event, -1);
...
if (res_event[0].data.fd == connfd) {// 该文件描述符是可读的文件描述符
if ((n = read(confd, buf, MAXLINE / 2)) == 0) { // 客户端主动断开连接了
res = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
if (res == -1)
perr_exit("epoll_ctl error");
close(sockfd);
}
else if (n < 0) {
perror("read n < 0 error: ");
res = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
}
else { // 文件描述符有数据可以读
while ((n = read(confd, buf, MAXLINE / 2)) > 0) { // 当n=0时表示的读到文件末尾,而不是对端关闭连接
write(STDOUT_FILENO, buf, n);
}
}