代码就是之前论坛发过的一个epoll代码,根据需要改了就是服务端send的参数。。如下:
复制代码
客户的
测试代码就是<Unix
网络
编程>中30章的那个client代码。
问题表现就是epoll这个的服务端对10000的并发请求处理特别慢,甚至还出现很多客户连接超时的情况!但是顺序的一个个请求没有问题。
测试如下:
首先是1个进程,顺序10000个请求。。服务端没问题,很快速完成。
然后是10000个进程,每个进程1个请求,前面都还正常,可是过一会服务端accept就阻塞了,大概有1-2s,之后又返回,有时候还会出现客户端连接超时的问题,但是这样测30章那个线程池(300个线程)的服务端代码,不管怎么测都不会有问题。
按理说accept应该能一直返回才对呀,为什么中途会阻塞呢?是 内核参数问题?
之前也试过把listenfd也添加到epoll里,listenfd不是ET模式。。也有这样的问题。。
分析了很多可能:
epoll本身处理效率的问题(这个自己都不信)
服务端完成客户的处理请求太耗时,导致没有 时间让accept返回其他客户连接(这个最简的单处理,应该也不会)
单台机器测试,所以产生了太多的TIME_WAIT导致客户无法连接导致超时(之前以为是这个原因)
内核的一些限制问题,服务端不能同时处理太多连接(可能的原因)
最终才发现真正原因!!!
原来上面这个服务器代码listen指定的backlog连接完成队列参数太小,只有32,导致高并发的时候,服务器的连接完成队列在极短的时间内被填满了,而accept的处理速度跟不上队列填满的速度,导致队列始终是满的,然后就不理会客户的其他连接请求,导致了客户connect超时,并且处理效率低下。
而线程池的backlog有1024,不过受限于内核参数的默认值最大128,所以线程池这个的backlog实际是128(见man listen),再加上300个线程,每个线程独自accpet,所以能很快从完成队列中取得连接,客户的connect也不会超时了,如果把线程数改为1个,客户连接也会超时。
下面是man listen中的引用
详细信息可以man listen。同时man tcp里面有很多限制对服务器来说需要改的。
网上看到的一个修改服务器参数的: http://hi.baidu.com/yupanlovehlq ... cc2155faf2c099.html
- /*-------------------------------------------------------------------------------------------------
- gcc -o httpd httpd.c -lpthread
- author: wyezl
- 2006.4.28
- ---------------------------------------------------------------------------------------------------*/
- #include <sys/socket.h>
- #include <sys/epoll.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <fcntl.h>
- #include <unistd.h>
- #include <stdio.h>
- #include <pthread.h>
- #include <errno.h>
- #include <string.h>
- #include <stdlib.h>
- #define PORT 8888
- #define MAXFDS 5000
- #define EVENTSIZE 100
- #define BUFFER "HTTP/1.1 200 OK\r\nContent-Length: 5\r\nConnection: close\r\nContent-Type: text/html\r\n\r\nHello"
- int epfd;
- void *serv_epoll(void *p);
- void setnonblocking(int fd)
- {
- int opts;
- opts=fcntl(fd, F_GETFL);
- if (opts < 0)
- {
- fprintf(stderr, "fcntl failed\n");
- return;
- }
- opts = opts | O_NONBLOCK;
- if(fcntl(fd, F_SETFL, opts) < 0)
- {
- fprintf(stderr, "fcntl failed\n");
- return;
- }
- return;
- }
- int main(int argc, char *argv[])
- {
- int fd, cfd,opt=1;
- struct epoll_event ev;
- struct sockaddr_in sin, cin;
- socklen_t sin_len = sizeof(struct sockaddr_in);
- pthread_t tid;
- pthread_attr_t attr;
- epfd = epoll_create(MAXFDS);
- if ((fd = socket(AF_INET, SOCK_STREAM, 0)) <= 0)
- {
- fprintf(stderr, "socket failed\n");
- return -1;
- }
- setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));
- memset(&sin, 0, sizeof(struct sockaddr_in));
- sin.sin_family = AF_INET;
- sin.sin_port = htons((short)(PORT));
- sin.sin_addr.s_addr = INADDR_ANY;
- if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) != 0)
- {
- fprintf(stderr, "bind failed\n");
- return -1;
- }
- if (listen(fd, 32) != 0)
- {
- fprintf(stderr, "listen failed\n");
- return -1;
- }
- pthread_attr_init(&attr);
- pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
- if (pthread_create(&tid, &attr, serv_epoll, NULL) != 0)
- {
- fprintf(stderr, "pthread_create failed\n");
- return -1;
- }
- while ((cfd = accept(fd, (struct sockaddr *)&cin, &sin_len)) > 0)
- {
- setnonblocking(cfd);
- ev.data.fd = cfd;
- ev.events = EPOLLIN | EPOLLET | EPOLLERR | EPOLLHUP | EPOLLPRI;
- epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
- //printf("connect from %s\n",inet_ntoa(cin.sin_addr));
- //printf("cfd=%d\n",cfd);
- }
- if (fd > 0)
- close(fd);
- return 0;
- }
- void *serv_epoll(void *p)
- {
- int i, ret, cfd, nfds;;
- struct epoll_event ev,events[EVENTSIZE];
- char buffer[512];
- while (1)
- {
- nfds = epoll_wait(epfd, events, EVENTSIZE , -1);
- //printf("nfds ........... %d\n",nfds);
- for (i=0; i<nfds; i++)
- {
- if(events[i].events & EPOLLIN)
- {
- cfd = events[i].data.fd;
- ret = recv(cfd, buffer, sizeof(buffer),0);
- //printf("read ret..........= %d\n",ret);
- ev.data.fd = cfd;
- ev.events = EPOLLOUT | EPOLLET;
- epoll_ctl(epfd, EPOLL_CTL_MOD, cfd, &ev);
- }
- else if(events[i].events & EPOLLOUT)
- {
- cfd = events[i].data.fd;
- // send第三个参数改为客户请求的字节数
- ret = send(cfd, BUFFER, atoi(buffer), 0);
- //printf("send ret...........= %d\n", ret);
- ev.data.fd = cfd;
- epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, &ev);
-
- close(cfd);
- }
- else
- {
- cfd = events[i].data.fd;
- ev.data.fd = cfd;
- epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, &ev);
- close(cfd);
- }
- }
- }
- return NULL;
- }
问题表现就是epoll这个的服务端对10000的并发请求处理特别慢,甚至还出现很多客户连接超时的情况!但是顺序的一个个请求没有问题。
测试如下:
首先是1个进程,顺序10000个请求。。服务端没问题,很快速完成。
然后是10000个进程,每个进程1个请求,前面都还正常,可是过一会服务端accept就阻塞了,大概有1-2s,之后又返回,有时候还会出现客户端连接超时的问题,但是这样测30章那个线程池(300个线程)的服务端代码,不管怎么测都不会有问题。
按理说accept应该能一直返回才对呀,为什么中途会阻塞呢?是 内核参数问题?
之前也试过把listenfd也添加到epoll里,listenfd不是ET模式。。也有这样的问题。。
分析了很多可能:
epoll本身处理效率的问题(这个自己都不信)
服务端完成客户的处理请求太耗时,导致没有 时间让accept返回其他客户连接(这个最简的单处理,应该也不会)
单台机器测试,所以产生了太多的TIME_WAIT导致客户无法连接导致超时(之前以为是这个原因)
内核的一些限制问题,服务端不能同时处理太多连接(可能的原因)
最终才发现真正原因!!!
原来上面这个服务器代码listen指定的backlog连接完成队列参数太小,只有32,导致高并发的时候,服务器的连接完成队列在极短的时间内被填满了,而accept的处理速度跟不上队列填满的速度,导致队列始终是满的,然后就不理会客户的其他连接请求,导致了客户connect超时,并且处理效率低下。
而线程池的backlog有1024,不过受限于内核参数的默认值最大128,所以线程池这个的backlog实际是128(见man listen),再加上300个线程,每个线程独自accpet,所以能很快从完成队列中取得连接,客户的connect也不会超时了,如果把线程数改为1个,客户连接也会超时。
下面是man listen中的引用
QUOTE:
If the backlog argument is greater than the value in /proc/sys/net/core/somaxconn, then it is silently truncated to that value; the default value in this file is
128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128.
128. In kernels before 2.4.25, this limit was a hard coded value, SOMAXCONN, with the value 128.
详细信息可以man listen。同时man tcp里面有很多限制对服务器来说需要改的。
网上看到的一个修改服务器参数的: http://hi.baidu.com/yupanlovehlq ... cc2155faf2c099.html
QUOTE:
$ /proc/sys/net/core/wmem_max
最大socket写buffer,可参考的优化值:873200
$ /proc/sys/net/core/rmem_max
最大socket读buffer,可参考的优化值:873200
$ /proc/sys/net/ipv4/tcp_wmem
TCP写buffer,可参考的优化值: 8192 436600 873200
$ /proc/sys/net/ipv4/tcp_rmem
TCP读buffer,可参考的优化值: 32768 436600 873200
$ /proc/sys/net/ipv4/tcp_mem
同样有3个值,意思是:
net.ipv4.tcp_mem[0]:低于此值,TCP没有 内存压力.
net.ipv4.tcp_mem[1]:在此值下,进入内存压力阶段.
net.ipv4.tcp_mem[2]:高于此值,TCP拒绝分配socket.
上述内存单位是页,而不是字节.可参考的优化值是:786432 1048576 1572864
$ /proc/sys/net/core/netdev_max_backlog
进入包的最大设备队列.默认是300,对重负载服务器而言,该值太低,可调整到1000.
$ /proc/sys/net/core/somaxconn
listen()的默认参数,挂起请求的最大数量.默认是128.对繁忙的服务器,增加该值有助于网络性能.可调整到256.
$ /proc/sys/net/core/optmem_max
socket buffer的最大初始化值,默认10K.
$ /proc/sys/net/ipv4/tcp_max_syn_backlog
进入SYN包的最大请求队列.默认1024.对重负载服务器,增加该值显然有好处.可调整到2048.
$ /proc/sys/net/ipv4/tcp_retries2
TCP失败重传次数,默认值15,意味着重传15次才彻底放弃.可减少到5,以尽早释放内核资源.
$ /proc/sys/net/ipv4/tcp_keepalive_time
$ /proc/sys/net/ipv4/tcp_keepalive_intvl
$ /proc/sys/net/ipv4/tcp_keepalive_probes
这3个参数与TCP KeepAlive有关.默认值是:
tcp_keepalive_time = 7200 seconds (2 hours)
tcp_keepalive_probes = 9
tcp_keepalive_intvl = 75 seconds
意思是如果某个TCP连接在idle 2个小时后,内核才发起probe.如果probe 9次(每次75秒)不成功,内核才彻底放弃,认为该连接已失效.对服务器而言,显然上述值太大. 可调整到:
/proc/sys/net/ipv4/tcp_keepalive_time 1800
/proc/sys/net/ipv4/tcp_keepalive_intvl 30
/proc/sys/net/ipv4/tcp_keepalive_probes 3
$ proc/sys/net/ipv4/ip_local_port_range
指定端口范围的一个配置,默认是32768 61000,已够大.
net.ipv4.tcp_syncookies = 1
表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1
表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout = 30
表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
net.ipv4.tcp_keepalive_time = 1200
表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.ip_local_port_range = 1024 65000
表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192
表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_tw_buckets = 5000
表示 系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为 5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。
一般设置:
1 sudo vi /etc/sysctl.conf
在最下面编辑添加:
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.route.gc_timeout = 100
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_max_syn_backlog = 262144
net.core.netdev_max_backlog = 262144
net.core.somaxconn = 262144
net.ipv4.tcp_mem = 94500000 915000000 927000000
保存退出
2 sudo /sbin/sysctl -p
最大socket写buffer,可参考的优化值:873200
$ /proc/sys/net/core/rmem_max
最大socket读buffer,可参考的优化值:873200
$ /proc/sys/net/ipv4/tcp_wmem
TCP写buffer,可参考的优化值: 8192 436600 873200
$ /proc/sys/net/ipv4/tcp_rmem
TCP读buffer,可参考的优化值: 32768 436600 873200
$ /proc/sys/net/ipv4/tcp_mem
同样有3个值,意思是:
net.ipv4.tcp_mem[0]:低于此值,TCP没有 内存压力.
net.ipv4.tcp_mem[1]:在此值下,进入内存压力阶段.
net.ipv4.tcp_mem[2]:高于此值,TCP拒绝分配socket.
上述内存单位是页,而不是字节.可参考的优化值是:786432 1048576 1572864
$ /proc/sys/net/core/netdev_max_backlog
进入包的最大设备队列.默认是300,对重负载服务器而言,该值太低,可调整到1000.
$ /proc/sys/net/core/somaxconn
listen()的默认参数,挂起请求的最大数量.默认是128.对繁忙的服务器,增加该值有助于网络性能.可调整到256.
$ /proc/sys/net/core/optmem_max
socket buffer的最大初始化值,默认10K.
$ /proc/sys/net/ipv4/tcp_max_syn_backlog
进入SYN包的最大请求队列.默认1024.对重负载服务器,增加该值显然有好处.可调整到2048.
$ /proc/sys/net/ipv4/tcp_retries2
TCP失败重传次数,默认值15,意味着重传15次才彻底放弃.可减少到5,以尽早释放内核资源.
$ /proc/sys/net/ipv4/tcp_keepalive_time
$ /proc/sys/net/ipv4/tcp_keepalive_intvl
$ /proc/sys/net/ipv4/tcp_keepalive_probes
这3个参数与TCP KeepAlive有关.默认值是:
tcp_keepalive_time = 7200 seconds (2 hours)
tcp_keepalive_probes = 9
tcp_keepalive_intvl = 75 seconds
意思是如果某个TCP连接在idle 2个小时后,内核才发起probe.如果probe 9次(每次75秒)不成功,内核才彻底放弃,认为该连接已失效.对服务器而言,显然上述值太大. 可调整到:
/proc/sys/net/ipv4/tcp_keepalive_time 1800
/proc/sys/net/ipv4/tcp_keepalive_intvl 30
/proc/sys/net/ipv4/tcp_keepalive_probes 3
$ proc/sys/net/ipv4/ip_local_port_range
指定端口范围的一个配置,默认是32768 61000,已够大.
net.ipv4.tcp_syncookies = 1
表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1
表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout = 30
表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。
net.ipv4.tcp_keepalive_time = 1200
表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.ip_local_port_range = 1024 65000
表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192
表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_tw_buckets = 5000
表示 系统同时保持TIME_WAIT套接字的最大数量,如果超过这个数字,TIME_WAIT套接字将立刻被清除并打印警告信息。默认为180000,改为 5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于Squid,效果却不大。此项参数可以控制TIME_WAIT套接字的最大数量,避免Squid服务器被大量的TIME_WAIT套接字拖死。
一般设置:
1 sudo vi /etc/sysctl.conf
在最下面编辑添加:
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.route.gc_timeout = 100
net.ipv4.ip_local_port_range = 1024 65000
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_max_syn_backlog = 262144
net.core.netdev_max_backlog = 262144
net.core.somaxconn = 262144
net.ipv4.tcp_mem = 94500000 915000000 927000000
保存退出
2 sudo /sbin/sysctl -p