如何编写一个高并发的tcp server

如何编写一个高并发的tcp server,第1张

服务器在调用listen和accept后,就会阻塞在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=0testCaseIndex<testCounttestCaseIndex++)

{

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) //在所有线程创建完成后才进行唤醒。

高并发C10K

C10k:服务器同时处理1W个TCP连接。

C10M:服务器同时处理1kw个TCP连接。

实现高并发的本质技术是 事件驱动 和 异步开发 。协程也是依靠这俩实现高并发的。

最简单的,就是一个 UDP请求 。

因为一个UDP请求通常仅由一个网络报文组成。

请求——事件——回调函数

因为常见的HTTP等协议都是基于TCP实现的,TCP是一个面向字节流的协议,所以会导致HTTP请求的大小不受限制。

当一个HTTP请求的大小超过TCP报文的最大长度的时候,就会被拆分为多个报文,然后在接收端的缓冲区重组和排序,所以说,不是每个到达的TCP报文都能生成事件(不完整)。

事件只有2种类型——

第二次握手结束,客户端->服务器的通道就建立好了,客户端就会产生写事件。

第三次握手,客户端发送的ACK报文到达服务器后,服务器产生读事件。

看着有些多此一举

写缓冲区,write函数,进程

关闭连接的时候,被动方会产生读事件,从而调用close函数关闭连接。

服务器里面对资源的操作从快到慢,依次是CPU,内存,磁盘,网络。

前两个都很快,所以不需要考虑事件驱动。

然而磁盘和网络都需要采用 事件驱动的异步方式 来处理。

当下的事件驱动,针对的都是网络事件。

网络事件是由内核产生的,进程使用epoll这样的多路复用技术就可以获取到它们(网络事件)。

太复杂了慢慢看

好我们这里直接进入总结——

网络报文到达后,内核就产生了读写事件,epoll函数的作用则是可以 让进程高效地收集这些事件 。

还要确保在进程中处理每个事件的时间足够短,从而才能及时地处理所有的请求——这个过程中,要避免使用 阻塞socket ,也要把耗时长的操作拆分成多份。

epoll多路复用是一种机制,可以实现C10M的高并发服务。

因为网络消息的传输第一比较慢,第二也不可控,所以用网络事件驱动请求的性价比最高。

TCP报文是如何产生事件的?

可以用多路复用技术获取事件。epoll的优势在于取消了收集事件的时候重复传递大量的socket参数。


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/385850.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-16
下一篇2023-05-16

发表评论

登录后才能评论

评论列表(0条)

    保存