TCP长连接的思考和相关问题的实验_百万链接数测试_百万链接内核参数的调整

origin:http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520138128552553/
TCP长连接的思考和相关问题的实验_百万链接数测试_百万链接内核参数的调整   

2013-09-12 21:01:43|  分类: Linux网络编程|举报|字号 订阅

  下载LOFTER 我的照片书  |
一、TCP长链接思考:
有一台机器,它有一个 IP,上面运行了一个 TCP 服务程序,程序只侦听一个端口,问:从理论上讲(只考虑 TCP/IP 这一层面,不考虑IPv6)这个服务程序可以支持多少并发 TCP 连接?
        具体来说,这个问题等价于: 有一个 TCP 服务程序的地址是 1.2.3.4:8765,问它从理论上能接受多少个并发连接?
  在只考虑 IPv4 的情况下,并发数的理论上限是 2**48。考虑某些 IP 段被保留了,这个上界可适当缩小,但数量级不变。实际的限制是操作系统全局文件描述符的数量( /proc/sys/fs/file-max ),以及内存大小(CPU不是瓶颈)。
一个 TCP 连接有两个 end points,每个 end point 是 {ip, port},具体是{serverip,serverport}和{clientip,clientport},题目说其中一个 end point 已经固定,即{serverip,serverport}, 那么留下一个 end point 的自由度,即 2 ** 48。原因客户端 IP 的上限是 2**32 个(IPV4,所有的IP数),每个客户端IP发起连接的上限是 2**16(local_port_range是一个unsigned short类型, 其实一般还要保留至少0~1024段),乘到一起得理论上限。(实际由于ip预留了一段作为私有IP,port预留了一段作为系统用,所以实际比这个要小,但是量级不变),即便客户端使用 NAT,也不影响这 个理论上限。(为什么?因为IP已经全部被计算在里面了)。


二、建立链接的速度限制:
       Listen 的端口有一个backlog配置,它的大小会影响监听端口上等待建立连接的队列长度,通过调整该值, 可以加速连接建立过程 。建议在 listen 函数和内核参数( /proc/sys/net/core/somaxconn )都设置为 1024

三、增加链接会影响链接上的发送和接收包速率吗?
        TCP 长连接个数对单个连接的收发包量和速率基本上不造成影响。Linux内核对连接采用4元组(serveripserverport,destipdestport)的hash管理,因此时间复杂度基本不随连接数目的变化而上升。

四、防火墙对TCP连接上收发包性能的影响:
        如果
系统配置了防火墙策略,并且开启防火墙的相关模块( nf_conntrack 等),系统会对每条连接进行跟踪。收发数据包时,需要在连接跟踪中查找相关连接信息,要消耗大量的 CPU 资源。
        防火墙操作的时间复杂度为O(N*M)N为连接数,M为数据包数,因此在海量连接情况下更加消耗CPU。
        参考资料:
                 http://hi.baidu.com/farmerluo/item/f49bbdc0e390a52dee4665bb
五、在真实的 Linux 系统中,TCP链接或者长连接受限受到内核参数的限制,如果想支持上百万的并发链接,就需要对内核参数进行调整:
可以参看:
        http://rdc.taobao.com/blog/cs/?p=1062
http://qa.blog.163.com/blog/static/19014700220134132571261/?latestBlog
http://qa.blog.163.com/blog/static/190147002201342115957410/
http://urbanairship.com/blog/2010/09/29/linux-kernel-tuning-for-c500k/
http://www.metabrew.com/article/a-million-user-comet-application-with-mochiweb-part-3
需要调整的内核参数如下:
1、ulimit -HSn 2000000 
     以上命令中,H指定了硬性大小,S指定了软性大小,n表示设定单个进程最大的打开文件句柄数量。
     ulimit 命令的理解:
    a、这个限制是针对单个程序的限制;
                  系统限制单个进程打开文件输的限制是:/proc/sys/fs/file-max,可以通过cat查看目前的值,用echo来立刻修改;
                  另外还有一个是/proc/sys/fs/file-nr只读,可以看到整个系统目前使用的文件句柄数量。
                  /proc/sys/fs/nr_open是进程打开文件资源的限制,可以用过echo来修改。
    b、这个限制不会改变之前已经运行了的程序的限制;
    c、对这个值的修改,退出了当前的shell就会消失,如果想保存其中一个方法,是想ulimit修改命令放入/etc/profile里面,但是这个做法并不好, 好的做法,应该是修改/etc/security/limits.conf里面有很详细的注释,比如
* soft nofile 1500000
* hard nofile 2000000
        就可以将文件句柄限制统一改成软1500000,硬20000000,这里涉及另外一个问题,什么是软限制,什么是硬限制,硬限制是实际的限制,而软限制,是warnning限制, 只会做出warning,其实ulimit命令本身就有分软硬设置,加-H就是硬,加-S就是软,默认显示的是软限制,如果修改的时候没有加上的话,就是两个一起改, 配置文件最前面的一位是domain,设置为星号代表全局,另外你也可以针对不同的用户做出不同的限制,修改了,重新登录用ulimit一看就立刻生效了,不过之 前启动过的程序要重新启动才能使用新的值。我用的是CentOS,似乎有些系统需要重启才能生效。
2、net.ipv4.ip_local_port_range   #本地IP端口的范围 
3、net.ipv4.tcp_rmem 
4、net.ipv4.tcp_wmem 
5、net.ipv4.tcp_mem
#3-5三个值的含义:a、低于此值,TCP没有内存压力;b、在此值下,进入内存压力阶段;c、高于此值,TCP拒绝分配socket,内存单位是页,而不是字节。 更多关于内核的网络参数详解及调优,可以参看: http://lijichao.blog.51cto.com/67487/308509
6、net.ipv4.tcp_max_orphans
         #系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。如果超过这个数字,孤儿连接将即刻被复位并打印出警告信息, 这个限制仅仅是为了防止简单的DoS攻击,你绝对不能过分依靠它或者人为地减小这个值,更应该增加这个值(如果增加了内存之后)。
        7、epoll监听fd个数的限制  一般是在/proc/sys/fs/epool/max_user_watches,在centos中好像是在fs.inotify.max_user_watches 中;
        8、/proc/sys/core/somaxconn                    listen fd backlog
在测试过程中,可以用dmesg查看内核有没有报一些错误。
六、几乎大家用到的测试方法都是使用很多客户端机器(也可能是虚拟机),是否可以通过虚拟网卡的方式来实现呢?
通过IP别名的方式进行最大链接数测试, 但是不幸的是,还是受到了ip_local_port_range的限制,还是不能超过0~65535的限制,没能成功(需要好好研究下),测试方法如下:
设置网卡的ip别名:
/sbin/ifconfig eth1:0 217.30.116.82 netmask 255.255.255.0 up;
/sbin/ifconfig eth1:1 217.30.116.83 netmask 255.255.255.0 up;
清除网卡的ip别名:
/sbin/ifconfig eth1:0 down;
ech1:* //虚拟网络接口,建立在eth1上,取值范围0~255
192.168.11.XXX //增加ip别名,想加多少就加多少~~~
使用/sbin/ifconfig查看是否生效,使用 ping 192.168.11.XXX查看通不通。
注意:
在设置ip别名时,如果增加的是和局域网同一个网段的ip(如192.168.11.100),那么 除了本机外局域网内其他机器都可以ping通这个机器ip,如果增加的是奇形的ip,那么只 有本机ping通而已,后者主要用户本机测试需要。
测试代码如下:
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <cstdio>
#include <string>
#include <iostream>
#include <stdexcept>
#include <set>
using namespace std;

int main(int argc,char *argv[])
{
if(argc<5)
{
printf("usage:%s localip serverip port connectnum\n",argv[0]);
exit(0);
}
unsigned int connect_num =strtoul(argv[4],NULL,10);
set<int> sock_set;
unsigned int csock_failed_num=0;
unsigned int setop_failed_num=0;
unsigned int bind_failed_num=0;
unsigned int conn_failed_num=0;
unsigned int index=0;
for(index=0;index<connect_num;index++)
{
int sock_fd=0;
sock_fd = socket(AF_INET,SOCK_STREAM,0); //创建socket
if(sock_fd < 0)
{
perror("creat sock_fd fail:");
//exit(0);
close(sock_fd);
csock_failed_num++;
continue;
}
int option_value = 1;                                                                                                //端口复用
int ret= setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,(char *)&option_value,sizeof(option_value));
if(ret < 0)
{
 perror("set sock opt fail:");
 close(sock_fd);
 //exit(0);
 close(sock_fd);
 setop_failed_num++;
 continue;
}
struct sockaddr_in connect_addr;         //客户端地址,主要绑定IP( 使用IP别名 )
connect_addr.sin_family = AF_INET;
connect_addr.sin_port = 0;         //0表示端口由操作系统分配
connect_addr.sin_addr.s_addr = inet_addr(argv[1]);
memset(connect_addr.sin_zero,'\0',sizeof(connect_addr.sin_zero));
//int ret;
ret=bind(sock_fd,(struct sockaddr*)&connect_addr,sizeof(connect_addr)); //绑定客户端IP
if(ret<0)
{
perror("bind client ip failed:");
//exit(0);
close(sock_fd);
bind_failed_num++;
continue;
}
struct sockaddr_in server_addr;           //服务端地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[3]));
server_addr.sin_addr.s_addr = inet_addr(argv[2]);
memset(server_addr.sin_zero,'\0',sizeof(server_addr.sin_zero));
socklen_t addr_len = sizeof(struct sockaddr_in);
ret = connect(sock_fd,(struct sockaddr*)&server_addr,addr_len);                           //可以适当坐下频率控制,防止server处理不过来
if(ret<0)
{
perror("connect to server failed:");
//exit(0);
close(sock_fd);
conn_failed_num++;
continue;
}
sock_set.insert(sock_fd);
}
while(true)
{
printf("connect socket num: conn=%u csock_failed_num=%u setop_failed_num=%u \
bind_failed_num=%u conn_failed_num=%u\n",
sock_set.size(),csock_failed_num,setop_failed_num,bind_failed_num,conn_failed_num);
sleep(10);
}
//close(sock_fd);
return 0;
}

IP别名的这种测试方法,理论上应该是可以的,可是自己此时没有成功,非常希望有相关经验的网友,能够留言,指出问题可能出现在哪里

猜你喜欢

转载自blog.csdn.net/yazhouren/article/details/51298252