在一个非阻塞的socket上调用read/write函数,返回EAGAIN或者EWOULDBLOCK(注:EAGAIN就是EWOULDBLOCK)。面试
从字面上看,意思是:编程
总结:服务器
这个错误表示资源暂时不够,可能read时, 读缓冲区没有数据, 或者write时,写缓冲区满了。 网络
遇到这种状况,若是是阻塞socket、 read/write就要阻塞掉。而若是是非阻塞socket、 read/write当即返回-1, 同 时errno设置为EAGAIN。socket
因此对于阻塞socket、 read/write返回-1表明网络出错了。但对于非阻塞socket、read/write返回-1不必定网络真的出错了。多是Resource temporarily unavailable。这时你应该再试,直到Resource available。tcp
综上, 对于non-blocking的socket,正确的读写操做为:函数
对于select和epoll的LT模式,这种读写方式是没有问题的。 但对于epoll的ET模式,这种方式还有漏洞。spa
epoll的两种模式 LT 和 ET.net
两者的差别在于 level-trigger 模式下只要某个 socket 处于 readable/writable 状态,不管何时进行 epoll_wait 都会返回该 socket;而 edge-trigger 模式下只有某个 socket 从 unreadable 变为 readable 或从unwritable 变为 writable 时,epoll_wait 才会返回该 socket。以下两个示意图:pwa
从socket读数据:
往socket写数据:
因此在epoll的ET模式下,正确的读写方式为:
正确的读:
n = 0; while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) { n += nread; } if (nread == -1 && errno != EAGAIN) { perror("read error"); }
int nwrite, data_size = strlen(buf); n = data_size; while (n > 0) { nwrite = write(fd, buf + data_size - n, n); if (nwrite < n) { if (nwrite == -1 && errno != EAGAIN) { perror("write error"); } break; } n -= nwrite; }
正确的accept,accept 要考虑 2 个问题:参考<<UNIX网络编程——epoll的 et,lt关注点>>讲解的更加详细
(1) LT模式下或ET模式下,阻塞的监听socket, accept 存在的问题
accept每次都是从已经完成三次握手的tcp队列中取出一个链接,考虑这种状况: 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 就表示全部链接都处理完。
综合以上两种状况,服务器应该使用非阻塞地 accept, accept 在 ET 模式下 的正确使用方式为:
while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote, (size_t *)&addrlen)) > 0) { handle_client(conn_sock); } if (conn_sock == -1) { if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) perror("accept"); }
一道腾讯后台开发的面试题:
使用Linux epoll模型,水平触发模式;当socket可写时,会不停的触发 socket 可写的事件,如何处理?
须要向 socket 写数据的时候才把 socket 加入 epoll ,等待可写事件。接受到可写事件后,调用 write 或者 send 发送数据。当全部数据都写完后,把 socket 移出 epoll。
这种方式的缺点是,即便发送不多的数据,也要把 socket 加入 epoll,写完后在移出 epoll,有必定操做代价。
开始不把 socket 加入 epoll,须要向 socket 写数据的时候,直接调用 write 或者 send 发送数据。若是返回 EAGAIN,把 socket 加入 epoll,在 epoll 的驱动下写数据,所有数据发送完毕后,再移出 epoll。
这种方式的优势是:数据很少的时候能够避免 epoll 的事件处理,提升效率。
最后贴一个使用epoll,ET模式的简单HTTP服务器代码:
#include <sys/socket.h> #include <sys/wait.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <sys/epoll.h> #include <sys/sendfile.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <strings.h> #include <fcntl.h> #include <errno.h> #define MAX_EVENTS 10 #define PORT 8080 //设置socket链接为非阻塞模式 void setnonblocking(int sockfd) { int opts; opts = fcntl(sockfd, F_GETFL); if(opts < 0) { perror("fcntl(F_GETFL)\n"); exit(1); } opts = (opts | O_NONBLOCK); if(fcntl(sockfd, F_SETFL, opts) < 0) { perror("fcntl(F_SETFL)\n"); exit(1); } } int main(){ struct epoll_event ev, events[MAX_EVENTS]; int addrlen, listenfd, conn_sock, nfds, epfd, fd, i, nread, n; struct sockaddr_in local, remote; char buf[BUFSIZ]; //建立listen socket if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { perror("sockfd\n"); exit(1); } setnonblocking(listenfd); bzero(&local, sizeof(local)); local.sin_family = AF_INET; local.sin_addr.s_addr = htonl(INADDR_ANY);; local.sin_port = htons(PORT); if( bind(listenfd, (struct sockaddr *) &local, sizeof(local)) < 0) { perror("bind\n"); exit(1); } listen(listenfd, 20); epfd = epoll_create(MAX_EVENTS); if (epfd == -1) { perror("epoll_create"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; ev.data.fd = listenfd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); } for (;;) { nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); if (nfds == -1) { perror("epoll_pwait"); exit(EXIT_FAILURE); } for (i = 0; i < nfds; ++i) { fd = events[i].data.fd; if (fd == listenfd) { while ((conn_sock = accept(listenfd,(struct sockaddr *) &remote,(size_t *)&addrlen)) > 0) { setnonblocking(conn_sock); //设置链接socket为非阻塞 ev.events = EPOLLIN | EPOLLET; //边沿触发要求套接字为非阻塞模式;水平触发能够是阻塞或非阻塞模式 ev.data.fd = conn_sock; if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock,&ev) == -1) { perror("epoll_ctl: add"); exit(EXIT_FAILURE); } } if (conn_sock == -1) { if (errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR) perror("accept"); } continue; } if (events[i].events & EPOLLIN) { n = 0; while ((nread = read(fd, buf + n, BUFSIZ-1)) > 0) { n += nread; } if (nread == -1 && errno != EAGAIN) { perror("read error"); } ev.data.fd = fd; ev.events = events[i].events | EPOLLOUT; if (epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1) { perror("epoll_ctl: mod"); } } if (events[i].events & EPOLLOUT) { sprintf(buf, "HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\nHello World", 11); int nwrite, data_size = strlen(buf); n = data_size; while (n > 0) { nwrite = write(fd, buf + data_size - n, n); if (nwrite < n) { if (nwrite == -1 && errno != EAGAIN) { perror("write error"); } break; } n -= nwrite; } close(fd); } } } close(epfd); close(listenfd); return 0; }