Linux TCP server系列(4)-浅谈listen与大并发TCP连接

背景:

   服务器在调用listenaccept后,就会阻塞在accept函数上,accpet函数返回后循环调用accept函数等待客户的TCP连接。如果这时候又大量的用户并发发起connect连接,那么在listen有队列上限(最大可接受TCP的连接数)的情况下,有多少个connect会成功了。试验证明,当连接数远远高于listen的可连接数上限时,客户端的大部分TCP请求会被抛弃,只有当listen监听队列空闲或者放弃某个连接时,才可以接收新的连接,那么我们应该如何来避免这种情况出现?

 

分析:

(一)客户端

客户端运行初期完成所设定的一定量的socket创建和相应的处理线程的创建,然后使用条件变量来完成线程同步,直到最后一个线程创建完成,才向所有线程发出广播通知,让所有线程并发调用connect,连接成功则关闭连接,失败则返回,如下代码所示。

socket创建和线程创建:

        int testCount=300;        //并发用户数

/*

每个进程需要自己独立的栈空间,linux下默认栈大小是10M,在32位的机子上一个进程需要4G的内存空间,去掉自己的栈空间全局程序段空间,一般只有3G内存可以用,创建线程时就需要从这3G的空间中分配10M出来,所以最多可以分配300个线程。当然这里还可以使用多个进程,每个进程300个线程的方式来进一步扩大并发量。

*/

        int sockfd[testCount];

        pthread_t ntid[testCount];

 

        bzero(&servaddr,sizeof(servaddr));

        servaddr.sin_family=AF_INET;

        servaddr.sin_port=htons(SERVER_PORT);

        inet_pton(AF_INET,argv[1],&servaddr.sin_addr);

 

     int testCaseIndex=0;

     for(testCaseIndex=0;testCaseIndex<testCount;testCaseIndex++)

     {

        sockfd[testCaseIndex]=socket(AF_INET,SOCK_STREAM,0);

                     //为每个并发客户端创建一个socket

        if(sockfd[testCaseIndex]==-1)

        {

           printf("socket established error: %s\n",(char*)strerror(errno));

           return -1;

        }

        if( pthread_create(&ntid[testCaseIndex],NULL,handleFun,&sockfd[testCaseIndex])!=0)

 {

                printf("create thread error :%s\n",strerror(errno));

                return -1;

 }

//为每个并发客户端创建一个线程来执行connect

}

 

     printf("%d client has initiated\n",testCaseIndex);

 

   并发客户端的线程实现:线程阻塞在条件变量上(只有条件满足了并且发起唤醒动作,线程才开始执行)。

        int sockfd=*((int*)arg);

        {

                pthread_cond_wait(&cond,&mut);

                                     //在条件变量上等待条件满足!

//阻塞返回后立即解锁,防止互斥量加锁带来的阻塞

pthread_mutex_unlock(&mut);

int conRes=0;

                conRes=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));

                                     //线程执行connect连接,每个线程在接到唤醒信号后,才可以执行该语句,来模拟多个线程的并发调用。

                if(conRes==-1)

                {

                  printf("connect error: %s\n",strerror(errno));

                  return 0;

                }

        }

 

   当条件满足时,唤醒阻塞在条件变量上的线程:

   while(1)

{

sleep(2);

pthread_cond_broadcast(&cond);  //在所有线程创建完成后才进行唤醒。

}

 

综上,客户端模拟并发过程中没有存在不同步的情况导致上述性能问题。(注意,在广播的时候,会出现广播丢失的情况,所以需要多次执行广播操作才会使得所有线程执行任务,所以某种程度上这里并不能模拟完完全全的并发)

 

   (二)通信中介

客户端和服务器之间的连接是在同一台机器上,使用Socket方式通信的话会经过127.0.0.1的回环线路,不会有网卡等硬件资源的访问性能消耗,所以不存在网络通信时延等问题。

 

   (三)服务器 

性能问题主要发生在服务器,可能是以下几部分造成:

1)服务器的监听队列 listen(listenfd,xxx),参数2指定队列内所能容纳的元素的最大值,当来不及从队列中移除元素时(调用accept移除或者TCP自动放弃)就会造成队列满而使得一些请求丢失。

解决办法:

a)增大队列容量是一种办法,但是注意等待队列太会带来效率的性能缺陷,而且listen函数对最大队列容量有一个上限,大小为SOMAXCONN,当然必要时刻我们可以修改这个常量的大小。
  b)直接修改listen及相关函数的实现(比较麻烦,不建议),可以将listen所维护的队列修改为linklist,支持队列的动态增长。

 

2accept处理速度太慢,导致阻塞过长时间,使得队列无法及时清空已经完成3次连接的socket。也就是任意两个accept之间的时间间隔是关键因素(这里实验了将accept后面的也删了,那么10个可以达到153的数目),如下面代码所示(listen之后在循环调用accept来将已完成3次握手的连接从listen所维护的队列中移除。)

listen(listenfd,10);

for(;;)

{

      chilen=sizeof(chiaddr);

connfd=accept(listenfd,(struct sockaddr*)&chiaddr,&chilen);

//其他操作

}

 

解决办法:

两个accept之间尽量不要又多余的操作,使得accept返回后可以立刻执行下一个accept经过试验,该方法可以较好的提高性能,减少connect的丢失数。

 

b

   本质上这是一种“生产者-消费者”的模式,listen维护“已连接”和“待连接”的队列,当客户发出连接请求并最终连接成功时,在“已连接”队列中会生产一个“product”,然后这时候希望“消费者”也就是accept函数可以快速的从队列中消费这个“product,这样就不会因为队列满而导致无法继续生产(也就是客户的connect会失效,导致上面队列长度10300个并发connect带来的67个存活的情况),但是在本例情况下,我们无法控制生产者的疯狂生产行为,因为连接是客户发起的,这是不可预知的,所以我们如果想不修改listen函数来提高性能的话,那么就只能让消费者更加快的把产品消耗掉,使得listen队列可以容纳更多的新生产的产品,而第一种加快消费者消耗产品的方法就是a,第二种加快消费者消耗产品的方法是我们可以增加多几个消费者来帮忙消耗,但是这几个消费者间也要好好协调。第三种方法是让消费者把产品先移走为listen的队列腾出空间,再自行处理产品,如d所示。

使用多线程策略,每个线程独立调用accept(花了一个上午的时候正glib的线程池,一直用不了,其他都正常,就是线程不启动,不知道会不会是bug)

下面自己手动使用简单的多线程来测试

线程数                队列容量                   并发用户数              通过数

1                            10                                 300                              65

2                            10                                 300                              142

3                            10                                 300                              122

6                            10                                 300                              120

10                          10                                 300                              196

 20                          10                                 300                              174

可以看到线程间也会出现竞争现象,并不是说一味增大并发线程数就可以提高并发数的。

 

c

修改listenaccept的实现方式,让listen所维护的队列可以智能的判断拥挤情况,从而对accept的调用做出调度,在队列繁忙时,使用多线程的方式让多个accept来移除队列中的元素,在队列空闲时,可以适当的调整accept的处理线程数,这也是一种线程池的实现。

 

 

  d

修改accept的实现方式,在accept中实现一个“消费缓冲区”,为的是及时将listen中的队列元素移动到该缓冲区中,再由其他处理线程或者进程来对缓冲区中的元素进行处理,这个方法尽量让listen队列中已连接的socket可以被移除。

最后这个方法比较上述方法来说是比较好的一种,但是还是需要修改已有的代码。

 server.cpp
 #include<sys/types.h>
 #include<sys/socket.h>
 #include<strings.h>
 #include<arpa/inet.h>
 #include<unistd.h>
 #include<stdlib.h>
 #include<stdio.h>
 #include<string.h>
 #include<errno.h>
 #include<signal.h>
 #include<sys/wait.h>
 #include<pthread.h>
 
 #define LISTEN_PORT 84
 void str_echo(int sockfd)            // 服务器收到客户端的消息后的响应
 {
     ssize_t n;
     char line[512];
 
     printf("ready to read\n");
 
     while( (n=read(sockfd,line,512))>0 )
     {
 
         line[n]='\0';
         printf("Client: %s\n",line);
 
         char msgBack[512];
         snprintf(msgBack,sizeof(msgBack),"recv: %s\n",line);
         write(sockfd,msgBack,strlen(msgBack));
         bzero(&line,sizeof(line));
 
     }
 
     printf("end read\n");
 
 }
 
 void sig_child(int signo)         //父进程对子进程结束的信号处理
 {
     pid_t pid;
     int   stat;
 
     while( (pid=waitpid(-1,&stat,WNOHANG))>0)
     printf("child %d terminated\n",pid);
 
     return;
 }
 
 void* acceptThreadFun(void *arg)
 {
     int listenfd=*((int*)arg);
     struct sockaddr_in chiaddr;
     socklen_t chilen;
     int connfd;
     for(;;)
     {
         chilen=sizeof(chiaddr);
         
         connfd=accept(listenfd, (struct sockaddr*)&chiaddr, &chilen);
         //accept会总listen所维护的已连接队列中pop一个出来
         //阻塞在accept,直到三次握手成功了才返回
         if(connfd==-1)
         printf("accept client error: %s\n",strerror(errno));
         else            
         printf("client connected :%d\n",1);
 
         close(connfd);
         
     }
 }
 int successCount=0;
 int main(int argc, char **argv)
 {
     int listenfd, connfd;
     pid_t childpid;
     socklen_t chilen;
 
     struct sockaddr_in chiaddr,servaddr;
 
     listenfd=socket(AF_INET,SOCK_STREAM,0);
     if(listenfd==-1)
     {
         printf("socket established error: %s\n",(char*)strerror(errno));                 //后面需要采用日志到方式来记录
         //socket创建失败后可以让用户选择重新连接
     }
 
     bzero(&servaddr,sizeof(servaddr));
     servaddr.sin_family=AF_INET;
     servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
     servaddr.sin_port=htons(LISTEN_PORT);
 
     int bindc=bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
     if(bindc==-1)
     {
         printf("bind error: %s\n",strerror(errno));
         //绑定失败,错误提示
     }
 
     listen(listenfd,10);               //limit是SOMAXCONN
 
     signal(SIGCHLD,sig_child);
 
     pthread_t acceptThread[20];
     int threadCount=0;
     for(threadCount;threadCount<20;threadCount++)
     {
         err=pthread_create(&acceptThread[threadCount],NULL,acceptThreadFun,&listenfd);    //创建线程来帮忙accept
         if(err!=0)
         printf("can not create thread : %s\n",strerror(errno));
     }
 
     for(;;)
     {
         chilen=sizeof(chiaddr);
         
         connfd=accept(listenfd,(struct sockaddr*)&chiaddr,&chilen);
         //accept会总listen所维护的已连接队列中pop一个出来
         //阻塞在accept,直到三次握手成功了才返回
         if(connfd==-1)
         printf("accept client error: %s\n",strerror(errno));
         else        
         printf("client connected :%d\n",++successCount);
         
         close(connfd);
         
     }
 
 }


#include<sys/types.h>
 2 #include<stdlib.h>
 3 #include<stdio.h>
 4 #include<unistd.h>
 5 #include<sys/socket.h>
 6 #include<strings.h>
 7 #include<string.h>
 8 #include<arpa/inet.h>
 9 #include<errno.h>
10 #include<stdio.h>
11 #include<pthread.h>
12 #include<algorithm>
13 
14 #include<exception>
15 
16 #define SERVER_PORT 84
17 
18 pthread_mutex_t mut=PTHREAD_MUTEX_INITIALIZER;    //互斥量
19 pthread_cond_t cond=PTHREAD_COND_INITIALIZER;  //条件变量
20 
21 int cond_value=1;
22 struct sockaddr_in servaddr;
23 
24 void *handleFun(void *arg)
25 {
26     int sockfd=*((int*)arg);
27 
28     {
29         pthread_cond_wait(&cond,&mut);    
30         pthread_mutex_unlock(&mut);
31         //信号会丢失,使得这里永远醒不了,所以需要重发信号.
32 
33         int conRes=0;
34         conRes=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
35 
36         printf("%d\n",sockfd); //如果不加connect,那么这里显示正确
37         if(conRes==-1)
38         {
39             printf("connect error: %s\n",strerror(errno));
40             return 0;
41         }
42 
43     }
44 
45 }
46 
47 void *handleFun2(void *arg)
48 {
49     *((int*)arg)=2;
50     pthread_cond_broadcast(&cond);
51 }
52 
53 int main(int argc, char **argv)
54 {
55     int testCount=300;
56     int sockfd[testCount];
57     pthread_t ntid[testCount];
58 
59     //tcpcli <ipaddress> <data>
60     if(argc!=3)
61     return -1;
62 
63     bzero(&servaddr,sizeof(servaddr));
64     servaddr.sin_family=AF_INET;
65     servaddr.sin_port=htons(SERVER_PORT);
66     inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
67 
68     int testCaseIndex=0;
69     for(testCaseIndex=0;testCaseIndex<testCount;testCaseIndex++)
70     {
71         sockfd[testCaseIndex]=socket(AF_INET,SOCK_STREAM,0);
72         //为每个客户端线程创建socket
73         if(sockfd[testCaseIndex]==-1)
74         {
75             printf("socket established error: %s\n",(char*)strerror(errno)); 
76 
77             return -1;
78         }
79 
80         if(pthread_create(&ntid[testCaseIndex],NULL,handleFun,&sockfd[testCaseIndex])!=0)
81         //客户端线程
82         {
83             printf("create thread error :%s\n",strerror(errno));
84             return -1;
85         }
86     }
87 
88     printf("%d client has initiated\n",testCaseIndex);
89 
90     cond_value=2;
91     while(1)
92     {
93         sleep(2);
94         pthread_cond_broadcast(&cond);  //条件满足后发信号通知所有阻塞在条件变量上的线程!
95     }
96     exit(0);
97 }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/434797.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

领航机器人广告段子_医院机器人物流广告词_段子网收录最新段子

瑞典RJO医疗供应商&#xff0c;机器人自助式物流领航者&#xff0c;拥有自己的机器人工作组&#xff0c;让医院处处散发出高科技。使用机器人物流体系&#xff0c;医院内部运输物料在标准化、可控、准时、高效方面有质的改变&#xff0c;大大提高医院对外形象。特征集广告词&am…

分享制作精良的知识管理系统 配置SQL Server文档数据库 完美实现博客文章的的下载,存储和浏览...

前一篇文章《分享制作精良的知识管理系统 博客备份程序 Site Rebuild》已经提到如何使用Site Rebuild来下载您所喜欢的博客文章&#xff0c;但是还不能实现把下载的文件导入进数据库中&#xff0c;无法实现在线浏览服务器中的文档数据。这一篇文章则帮助您建立文档数据库&#…

python绘制敏感性和特异性曲线(交叉)

示例 代码如下 test_fpr, test_tpr, test_thresholds roc_curve(y_test_two, dataset_blend_test_pred, pos_label1) test_roc_auc auc(test_fpr, test_tpr) plt.plot(test_thresholds, 1-test_fpr, labelspecificity) plt.plot(test_thresholds, test_tpr, labelsensitivit…

python迭代器创建序列_Python 中迭代器与生成器实例详解

Python 中迭代器与生成器实例详解本文通过针对不同应用场景及其解决方案的方式&#xff0c;总结了Python中迭代器与生成器的一些相关知识&#xff0c;具体如下&#xff1a;1.手动遍历迭代器应用场景&#xff1a;想遍历一个可迭代对象中的所有元素&#xff0c;但是不想用for循环…

一个网络资深者发起的思考

陈硕 (giantchen AT gmail) blog.csdn.net/Solstice 前几天我在新浪微博上出了两道有关 TCP 的思考题&#xff0c;引发了一场讨论 http://weibo.com/1701018393/eCuxDrta0Nn 。 第一道初级题目是&#xff1a; 有一台机器&#xff0c;它有一个 IP&#xff0c;上面运行了一个 TCP…

The Ransom of Red Chief

We can kidnap someone here. Who? Theres nobody rich in this town. The richest man in town, of course. Kidnap      绑架 rich         富 trouble      麻烦 Im not having a nice day. How much money have we got? Only two hundred dollars. Ra…

C++课堂整理--第一章内容

提前声明&#xff1a; 本文内容为华北水利水电大学研究生C课程&#xff0c;如有 侵权请告知&#xff0c;作者会予以删除 1.C特点 1.历史悠久。2.应用广泛。3.兼容c。4.面向对象。5.适合编写系统程序。6.有助于理解计算机的工作过程&#xff0c;深入理解计算机的原理和概念 …

oauth2 access_denied 不允许访问_OAuth 2 是什么-入门介绍

OAuth 2是什么OAuth 2是一个可以通过浏览器&#xff0c;手机等多种设备进行安全授权的一个标准简单的开源协议。随着互联网的兴起以及普及&#xff0c;越来越多的应用出现在用户的面前。这些应用大部分都是相对独立的以及由不同的公司进行运营的。不同的应用也保存了不同的数据…

基于TCP协议的网络程序(基础学习)

下图是基于TCP协议的客户端/服务器程序的一般流程&#xff1a; 图 37.2. TCP协议通讯流程 服务器调用socket()、bind()、listen()完成初始化后&#xff0c;调用accept()阻塞等待&#xff0c;处于监听端口的状态&#xff0c;客户端调用socket()初始化后&#xff0c;调用connect(…

云谊网-赴日人才社交网络

云谊网是为已赴日和准备赴日从事IT工作的人才提供经验交流的的社交网络平台&#xff0c;在这里您可以拓展人脉、发现机遇、推荐职位、分享知识。网址&#xff1a;http://club.sinowell.net/ 全国各IT领军城市软件和服务外包QQ群(ITO、BPO、HRO) 群描述&#xff1a;IT公司之间的…

一段代码认识C++中const不同位置的用处

#include<iostream> using namespace std ; int main () { const int A 78 ;const int B 25 ;int C 13 ;//---------const在数据类型前-------------------- const int *pi &A ;//*pi 56 ;// 错误, 不能修改所指常量。此时*pi指向的是常量A。 pi &…

怎么形容智能冰激凌机器人_有关于形容描写冰激凌的句子及图片

1、我拿出冰淇淋&#xff0c;隐隐约约的看见淡黄的雪梨果肉和黑乎乎的巧克力豆。嗯&#xff0c;牛奶的香醇中又夹杂着雪梨的清甜和巧克力的浓香&#xff0c;味道好极了。2、冰淇淋上面有各种各样颜色&#xff0c;有红的、黄的、紫的……真像一个八宝饭。3、冰淇淋的形状看上去像…

wcf系列学习5天速成——第五天 服务托管

今天是系列的终结篇&#xff0c;当然要分享一下wcf的托管方面的知识。 wcf中托管服务一般有一下四种&#xff1a; Console寄宿&#xff1a; 利于开发调试&#xff0c;但不是生产环境中的最佳实践。 winform寄宿&#xff1a; 方便与用户进行交互&#x…

SELECT的学习以及在socket中的应用

Select在Socket编程中还是比较重要的&#xff0c;可是对于初学Socket的人来说都不太爱用Select写程序&#xff0c;他们只是习惯写诸如 connect、accept、recv或recvfrom这样的阻塞程序&#xff08;所谓阻塞方式block&#xff0c;顾名思义&#xff0c;就是进程或是线程执行到这…

python钻石数据分析_数据分析该用什么工具?

数据分析的软件很多&#xff0c;完整的数据分析一般分为数据收集、处理、分析和展现四个步骤。下面分别介绍一下过程中每个步骤使用到的工具。Excel微软办公套装软件的一个重要的组成部分&#xff0c;它包含数据的基本处理&#xff0c;函数计算&#xff0c;数据透视表和VBA等多…

ASP.NET MVC Music Store教程(2):控制器

ASP.NET MVC Music Store教程&#xff08;2)&#xff1a;控制器 转自http://firechun.blog.163.com/blog/static/3180452220110272197830/在传统的Web架构中&#xff0c;URL总是映射到磁盘上的文件。例如&#xff1a;一个类似于“/Products.aspx”或“/Products.php”的URL可能…

C语言:利用泰勒级数计算sinx的值

题目&#xff1a; 代码&#xff1a; #include<stdio.h> #include<math.h> int main(){int sign1,n1;double x3,term,a,sinx0;//scanf("%lf",&x);termx;while(fabs(term)>0.00001){sinxsign*term;nn2;apow(x,n);double b1;for(int i1;i<n;i){…

搞IT也不能不懂“五险一金”啊

养老保险&#xff1a; 一般要交满15年&#xff0c;到退休的时候才能终生享受养老金, 所以想拿养老金的人请务必在自己退休前15年就开始交。如果到退休年龄交养老保险不满15年,那等到你退休的时候国家会把你个人帐户上存的8%的养老金全 部退给你。那单位给你交的21%到哪里去了?…

DecExpress 帮助网站

官方地址&#xff1a;http://devexpress.com 官方下载地址&#xff1a;http://downloads.devexpress.com/8981e647-2ebc-4bd4-b50c-15ca357a62e2/0.0.0.0/DXperience/2011.1/5/DXperienceUniversal-11.1.5.exe 破解地址&#xff1a; smartsoft论坛&#xff1a; http://smartsof…

elementui获取所有树节点_element-ui tree获取子节点全选的父节点信息

公司服务升级确定了新的架构&#xff0c;假如当前部门是二级部门&#xff0c;二级部门下的三级部门全部已选择&#xff0c;那么后端接口要求只需要传二级部门的id&#xff0c;并且操作符传参为 like&#xff1b;如果某一个部门已选择&#xff0c;切父级部门没有选择&#xff0c…