当进程用完文件描述符时,accept()将失败并将errno设置为EMFILE.
但是,已接受的基础连接未关闭,因此似乎无法通知客户端应用程序代码无法处理连接.
但是,已接受的基础连接未关闭,因此似乎无法通知客户端应用程序代码无法处理连接.
问题是在用完文件描述符时接受TCP连接的正确行动是什么.
以下代码演示了我想学习如何最好地处理的问题(注意这只是用于演示问题/问题的示例代码,而不是生产代码)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> static void err(const char *str) { perror(str); exit(1); } int main(int argc,char *argv[]) { int serversocket; struct sockaddr_in serv_addr; serversocket = socket(AF_INET,SOCK_STREAM,0); if(serversocket < 0) err("socket()"); memset(&serv_addr,sizeof serv_addr); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr= INADDR_ANY; serv_addr.sin_port = htons(6543); if(bind(serversocket,(struct sockaddr*)&serv_addr,sizeof serv_addr) < 0) err("bind()"); if(listen(serversocket,10) < 0) err("listen()"); for(;;) { struct sockaddr_storage client_addr; socklen_t client_len = sizeof client_addr; int clientfd; clientfd = accept(serversocket,(struct sockaddr*)&client_addr,&client_len); if(clientfd < 0) { continue; } } return 0; }
gcc srv.c ulimit -n 10 strace -t ./a.out 2>&1 |less
在另一个控制台中,我跑了
telnet localhost 65432 &
在accept()失败之前需要多次:
13:21:12 socket(AF_INET,IPPROTO_IP) = 3 13:21:12 bind(3,{sa_family=AF_INET,sin_port=htons(6543),sin_addr=inet_addr("0.0.0.0")},16) = 0 13:21:12 listen(3,10) = 0 13:21:12 accept(3,sin_port=htons(43630),sin_addr=inet_addr("127.0.0.1")},[128->16]) = 4 13:21:19 accept(3,sin_port=htons(43634),[128->16]) = 5 13:21:22 accept(3,sin_port=htons(43638),[128->16]) = 6 13:21:23 accept(3,sin_port=htons(43642),[128->16]) = 7 13:21:24 accept(3,sin_port=htons(43646),[128->16]) = 8 13:21:26 accept(3,sin_port=htons(43650),[128->16]) = 9 13:21:27 accept(3,0xbfe718f4,[128]) = -1 EMFILE (Too many open files) 13:21:27 accept(3,[128]) = -1 EMFILE (Too many open files) ... and thousands upon thousands of more accept() failures.
基本上在这一点上:
>代码将尽可能快地调用accept(),无法一次又一次地接受相同的TCP连接,从而搅拌cpu.
>客户端将保持连接状态(因为TCP握手在应用程序接受连接之前完成),并且客户端不会获得有问题的信息.
所以,
>有没有办法强制TCP连接导致accept()无法关闭(例如,客户端可以快速通知,也许可以尝试其他服务器)
>当出现这种情况时(或者完全防止这种情况),防止服务器代码进入无限循环的最佳做法是什么?
解决方法
您可以在程序开头留出额外的fd并跟踪EMFILE条件:
int reserve_fd; _Bool out_of_fd = 0; if(0>(reserve_fd = dup(1))) err("dup()");
然后,如果您点击EMFILE条件,您可以关闭reserve_fd并使用其插槽接受新连接(然后您将立即关闭):
clientfd = accept(serversocket,&client_len); if (out_of_fd){ close(clientfd); if(0>(reserve_fd = dup(1))) err("dup()"); out_of_fd=0; continue; /*doing other stuff that'll hopefully free the fd*/ } if(clientfd < 0) { close(reserve_fd); out_of_fd=1; continue; }
完整的例子:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> static void err(const char *str) { perror(str); exit(1); } int main(int argc,0); if(serversocket < 0) err("socket()"); int yes; if ( -1 == setsockopt(serversocket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) ) perror("setsockopt"); memset(&serv_addr,10) < 0) err("listen()"); int reserve_fd; int out_of_fd = 0; if(0>(reserve_fd = dup(1))) err("dup()"); for(;;) { struct sockaddr_storage client_addr; socklen_t client_len = sizeof client_addr; int clientfd; clientfd = accept(serversocket,&client_len); if (out_of_fd){ close(clientfd); if(0>(reserve_fd = dup(1))) err("dup()"); out_of_fd=0; continue; /*doing other stuff that'll hopefully free the fd*/ } if(clientfd < 0) { close(reserve_fd); out_of_fd=1; continue; } } return 0; }
如果你是多线程的,那么我想你需要锁定fd生成函数并在关闭额外的fd(同时期望接受最终连接)时接受它,以防止备用槽由另一个线程填充.
所有这一切只有在1)监听套接字没有与其他进程共享(可能还没有达到其EMFILE限制)和2)服务器处理持久连接时才有意义(因为如果它没有,那么你’必须很快关闭一些现有的连接,释放一个fd插槽,以便下次尝试接受).