今天准备写一下epoll_timer(即epoll实现的简单定时器, 后面会分享blog)由于用到epoll的模型,翻出原先的代码跑了一下,看到原来define的最大的处理用户上限,感觉有些不妥,所以决定测试一下我的ubuntu 16.04,1G内存的单机上究竟可以建立多少个连接。虽然网上有很多这方面的案例,但是我还是决定自己测试一下,印象深刻,对问题场景有更深的印象。
如果文中有错误或者不全面的地方,希望大家指正。
我测试代码是自己写的,也很简单,就是客户端程序不停的请求连接(这里都是短链接,长连接和需要数据交互的连接请求情况可能和本文的情况不同),服务器接收了客户端的请求建立连接,这种情况下,客户端不停的创建socket描述符,起初我的系统设置的上限是1024(ulimit -n查看)。
测试程序代码:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <netinet/in.h> #include <errno.h> #include <arpa/inet.h> #include <string.h> #include <signal.h> #include <sys/wait.h> #define ERR_EXIT(m)\     do{\         perror(m); \         exit(1); \     }while(0) int main(void) {      int socketfd, connfd;     while(1)     {     socketfd = socket(AF_INET, SOCK_STREAM, 0);     if(socketfd < 0)         ERR_EXIT("SOCKET");     printf("socketfd = %d\n", socketfd);     struct sockaddr_in serveraddr;     memset(&serveraddr, 0, sizeof(serveraddr));     serveraddr.sin_family = AF_INET;     serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");     serveraddr.sin_port = htons(6888);      //无限循环建立连接,不做数据处理     int opt = 1;     int ret2 = setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));     if(ret2 < 0)         ERR_EXIT("setsockopt");         printf("1\n");         socklen_t socklen = sizeof(serveraddr);         connfd = connect(socketfd, (const struct sockaddr*)&serveraddr, socklen);         if(connfd < 0)             ERR_EXIT("connfd");     }     close(socketfd); }服务器代码:
#include  <unistd.h> #include  <sys/types.h>       /* basic system data types */ #include  <sys/socket.h>      /* basic socket definitions */ #include  <netinet/in.h>      /* sockaddr_in{} and other Internet defns */ #include  <arpa/inet.h>       /* inet(3) functions */ #include <sys/epoll.h> /* epoll function */ #include <fcntl.h>     /* nonblocking */ #include <sys/resource.h> /*setrlimit */  #include <stdlib.h> #include <errno.h> #include <stdio.h> #include <string.h>  #define MAXEPOLLSIZE 65535 #define MAXLINE 10 int setnonblocking(int sockfd) {     if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {         return -1;     }     return 0; }  static int iCount = 0; static int iFaild = 0; int main(void) {     int  servPort = 6888;     int listenq = 65535;      int listenfd, connfd, kdpfd, nfds, n, curfds, acceptCount = 0;     struct sockaddr_in servaddr, cliaddr;     socklen_t socklen = sizeof(struct sockaddr_in);     struct epoll_event ev;     struct epoll_event events[MAXEPOLLSIZE];     struct rlimit rt;     char buf[MAXLINE];      /* 设置每个进程允许打开的最大文件数 */     rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;     if (setrlimit(RLIMIT_NOFILE, &rt) == -1){         perror("setrlimit error");         return -1;     }     bzero(&servaddr, sizeof(servaddr));     servaddr.sin_family = AF_INET;      servaddr.sin_addr.s_addr = htonl (INADDR_ANY);     servaddr.sin_port = htons (servPort);      listenfd = socket(AF_INET, SOCK_STREAM, 0);      if (listenfd == -1) {         perror("can't create socket file");         return -1;     }      int opt = 1;     setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));      if (setnonblocking(listenfd) < 0) {         perror("setnonblock error");     }      if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) == -1){         perror("bind error");         return -1;     }      if (listen(listenfd, listenq) == -1){         perror("listen error");         return -1;     }     /* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */     kdpfd = epoll_create(MAXEPOLLSIZE);     //ev.events = EPOLLIN | EPOLLET;     ev.events = EPOLLIN;     ev.data.fd = listenfd;     if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listenfd, &ev) < 0){         fprintf(stderr, "epoll set insertion error: fd=%d\n", listenfd);         return -1;     }     curfds = 1;      printf("epollserver startup,port %d, max connection is %d, backlog is %d\n", servPort, MAXEPOLLSIZE, listenq);      for (;;) {         /* 等待有事件发生 */         nfds = epoll_wait(kdpfd, events, curfds, -1);         if (nfds == -1){             perror("epoll_wait");             continue;         }         printf("epoll_wait return\n");                  /* 处理所有事件 */         for (n = 0; n < nfds; ++n){             if (events[n].data.fd == listenfd){                 connfd = accept(listenfd, (struct sockaddr *)&cliaddr,&socklen);                 if (connfd < 0){                     iFaild ++;                     if(iFaild >= 10)                         close(listenfd);                     perror("accept error");                     continue;                 }                 iCount++;                 sprintf(buf, "accept form %s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);                 printf("%d:%s  %d", ++acceptCount, buf, iCount);                 close(connfd);                 continue;                 }          }     }     close(listenfd);     return 0; }服务器代码采用epoll,逻辑也简单,有必要说明一点,需要注意close(connfd)的位置,连接建立立马断开连接。还有需要设置SO_REUSEADDR。
ulimit -n 1024情况下:

这时候建立的连接数是1021个短链接,客户端请求了1021个socket描述符,012三个描述符被占用。
单机处理1024个连接轻而易举。
我们来看看提高file descriptors的上限,ulimit -n 65535(端口号是16位无符号整数)
我们再来运行,需要一点时间。
结果是:

意思是,没有可用端口了,我们从服务器端看看一共建立了多少次连接

你会不会想这个数据从何而来?我可以告诉你,其实如果你的机器速度快,这个数据在默认情况下应该是28232,为什么这莫说呢?别忘了我们建立的是短链接,我们的服务器是主动关闭连接的一方,熟悉tcp四挥手的你应该想知道主动关闭的一方在调用了close之后会进入TIME_WAIT状态,我之所以说我的这个数据是28386是因为我的运行时间大于linux默认的timewait时间,有的端口可以复用了,所以这个数值大于28233,那么28232是怎么来的呢?
我们运行这一条命令:
sysctl -a | grep port
得到如下结果:

注意这一行:系统给我们的可用端口的范围是32768-60999,我们计算60999-32768+1正好是28232。所以说我们可以修改这个参数范围来提高支持的连接数。上面还提到产生了大量的timewait,我通过命令:
netstat -nt | awk '/^tcp/ {++state[$NF]} END {for(key in state) print key,"\t",state[key]}' 查看,也可以使用这个命令,结果相同。

用netstat -tcp更加直观:

提一下,之前我的测试服务器close(connfd)这一句并不是建立连接立即调用的,而是等客户端关闭然后服务器才调用,所以服务器是被动关闭的一方,你也可以试一试,这时候服务器会产生大量的closewait:
可能CLOSE_WAIT产生的原因:

大多数都是close的问题。
通过我的测试,我们发现,通过epoll实现10k真的很容易了,那么1g内存究竟可以建立多少连接呢,你能请求的文件描述符最大上限是多少呢?
你可以使用这个命令查看最大的文件句柄数,这个数值内存有关
我的机器可以支持的最大数量是:
不到10m连接,所以我单机想要实现10万以上的连接可以考虑增加系统内存。
综上测试,我得到一个结论,要想尽可能提高系统处理链接的数量,达到单机处理10m连接,我们可以采取以下措施:
1.通过ulimit -n来设置文件描述符打开的上限,这个修改是暂时的。你可以从这篇文章中得到永久修改的方法:点击打开链接
2.由于port_rang系统给我们可用port的限制,我们可以修改port_rang把区间调大
3.我们发现在这个过程中,产生很多的time_wait状态,倒置端口无法复用,我们如果能使多口复用岂不是很美?
你可以google大量timewait的解决办法,网上的方法很多,这里你可以参考这个https://www.jianshu.com/p/44655bff60a4
很精髓了。
4.增大系统内存,往往我们的内存不可能全部用来给你建立连接申请端口,所以我们可以减少tcp通信的过程的内存消耗,修改内核缓冲区的大小:
