一:客户端

本章总结的服务器程序设计范式,使用同一个客户端程序进行测试。个连接。使用的命令如下:

client  206.62.226.36  8888  5  500  4000

二:结论

上图测量的时间仅仅是用于进程控制所需的CPU时间,以迭代服务器为基准(原因见下一节),从其他服务器的实际CPU时间中减去迭代服务器的实际CPU时间,就得到相应服务器用于进程控制所需的CPU时间。因为迭代服务器没有进程控制。

上图描述了,在预先创建子进程池的设计范式中,闲置子进程过多对CPU时间的影响。

上图描述了,在预先创建子进程池和线程池的设计范式中,5000个连接在所有子进程或线程中的分布情况。

三:TCP迭代服务器

迭代TCP服务器总是在完全处理完某个客户的请求之后,才去处理下一个客户。因此这样的服务器程序比较少见。本章中用它作为测试基准,使用的测试命令如下:

client  206.62.226.36  8888  1  5000  4000

使用同样的TCP连接数5000,请求一样的字节数4000,但是同一时间只有一个连接,不进行进程控制,因而它的时间是最快的。

四:TCP并发服务器程序,每个客户一个子进程

传统上,并发服务器调用fork派生一个子进程来处理每个客户。这使得服务器能够同时为多个客户服务,每个进程一个客户。

绝大多数TCP服务器程序也按照这个范式编写。但是并发服务器的问题在于为每个客户现场fork一个子进程比较耗费CPU时间。在多年前的20世纪80年代后期,一个繁忙的服务器每天也就处理几百个亦或几千个客户,这点CPU时间是可以接受的。然而WEB应用的爆发式增长改变了人们的态度。繁忙的Web服务器每天测得TCP连接数以百万计,这还是就单个主机而言。

以后讲解的各种技术,都是在避免并发服务器为每个客户现场fork的做法,不过传统意义上的并发服务器依然相当普遍。

结论图1中的数据表明,传统意义的并发服务器所需CPU时间最多。

五:TCP预先派生子进程,accept无上锁保护

使用预先派生子进程的技术,不像传统意义上的并发服务器那样,为每个客户现场派生子进程,而是在启动阶段预先派生一定数量的子进程,当各个客户连接到达时,这些子进程立即就能为他们服务。

代码结构是:父进程启动完所有子进程之后,就进入睡眠。每个子进程中:

for(; ;)

{

connfd = accept(listenfd,  cliaddr,  &clilen);

web_child(connfd);

close(connfd);

}

在这种程序范式中,因为所有子进程和父进程共享同一个监听套接字。所以,当第一个客户连接到达时,所有子进程均被唤醒,因为所有子进程使用的监听套接字指向同一个socket结构。只有最先运行的那个子进程能获得客户连接,而其他子进程只能继续回复睡眠。

这种现象称为惊群(thundering herd)问题。因为尽管只有一个子进程将获得连接,所有其他子进程却都被唤醒了。尽管如此这段代码依然起作用,只是每仅有一个连接准备好时,却唤醒太多进程的做法会导致性能受损。结论图2的数据表明了这种问题。

结论图3给出了所有连接在子进程中的分布情况,可见当可用子进程阻塞在accept调用上时,内核调度算法把各个连接均匀地散布到各个子进程。

六:TCP预先派生子进程,accept使用上锁保护

有些系统不允许多个进程监听同一个套接字,在这些系统中,上述代码启动不久,某个子进程的accept可能就会返回EPROTO错误。

解决办法是让每个子进程在调用accept前后安置某种类型的锁,这样任意时刻只有一个子进程阻塞在accept调用中,其他子进程则阻塞在试图获取用于保护accept的锁上。子进程中的代码结构如下:

for(; ;)

{

my_lock_wait();

connfd = accept(listenfd,  cliaddr, &clilen);

my_lock_release();

web_child(connfd);

close(connfd);

}

有多种方法可以提供上锁功能,可以使用fcntl函数记录锁,也可以使用线程互斥锁pthread_mutex_lock进行进程上的上锁。

根据结论图1,可见这种上锁增加了服务器的进程控制CPU时间,但是线程互斥锁还是要快于记录锁的。

根据结论图3,看见上锁时,操作系统还是均匀的把锁散步到等待线程中。

七:TCP预先派生子进程,传递描述符

这种设计是只让父进程调用accept,然后把所接受的已连接套接字“传递”给某个子进程。这么做绕过了为所有子进程的accept调用提供上锁保护的需求,不过需要从父进程到子进程的某种形式的描述符传递。这种技术会使代码多少有点复杂,因为父进程必须跟踪子进程的忙闲状态,以便给空闲子进程传递新的套接字。

子进程中的代码结构如下,子进程阻塞在read_fd调用中,等待父进程传递描述符,收到描述符之后,处理客户请求。子进程在处理完客户请求之后,会向管道中写入一个字节,以通知父进程本子进程可重用(闲置状态):

for ( ; ; )

{

if ( (n =
Read_fd(sockfd[1],  &c,  1,  &connfd))== 0)

err_quit("read_fd returned0");

if (connfd < 0)

err_quit("no descriptorfrom read_fd");

web_child(connfd);      /* process request */

Close(connfd);

Write(STDERR_FILENO, "", 1);    /* tell parent we're ready again */

}

根据结论图1,可见本服务器慢于所有其他子进程池的设计。

八:TCP并发服务器,每个客户一个线程

使用线程代替子进程,客户连接到来时,现场创建一个线程处理客户请求,代码结构如下:

主线程:

for ( ;  ; )

{

clilen = addrlen;

connfd = Accept(listenfd,  cliaddr,  &clilen);

Pthread_create(&tid,  NULL,  &doit, (void *) connfd);

}

子线程:

void  * doit(void *arg)

{

void   web_child(int);

Pthread_detach(pthread_self());

web_child((int) arg);

Close((int) arg);

return (NULL);

}

表明,这个简单的创建线程的版本快于所有预先派生子进程的版本。

九:TCP预先创建线程,每个线程各自accept

既然预先派生子进程快于为每个客户现场派生一个子进程,那么有理由相信预先创建一个线程池也快于为每个客户现场创建一个线程的做法。本节中的设计,是让每个线程各自调用accept,使用互斥锁加以保护,以保证任何时刻只有一个线程在调用accept。

结论图1表明,这样的设计确实快于每个客户现场一个线程的版创建线程池,事实上当前版本的服务器是所有版木之中最快的。

结论图3表明,所有连接也均匀的分布在各个线程上了,这种均衡性是由线程调度算法带来的。

十:TCP预先创建线程,主线程统一accept

主线程在创建一个线程池之后,只让主线程调用accept,并把每个客户连接传递给池中某个可用线程。

本设计范式的问题在于,主线程如何把一个已连接套接字传递给线程池中某个可用线程。

可以如前使用描述符传递,不过既然所有线程和所有描述符都在同一个进程之内,我们没有必要把一个描述符从一个线程传递到另一个线程。接收线程只需知道这个已连接套接字描述符的值,而描述符传递实际传递的井非这个值,而是对这个套接字的一个引用,因而将返回一个不同于原值的描述符。

所以,在多线程环境中,这实际上又是一个生产者消费者问题,主线程为一个生产者,所有子线程为多个消费者。

根据结论图1,可见这个版本的服务器慢于上一节中的互斥锁保护accept的版本,这是因为生产者和消费者之间的同步问题造成的。

摘自《Unix网络编程卷一:套接字联网API》第30章

UNP服务器设计范式总结的更多相关文章

  1. Linux网络编程客户\服务器设计范式

    1.前言 网络编程分为客户端和服务端,服务器通常分为迭代服务器和并发服务器.并发服务器可以根据多进程或多线程进行细分,给每个连接创建一个独立的进程或线程,或者预先分配好多个进程或线程等待连接的请求.今 ...

  2. Linux C++服务器程序设计范式

    <Unix网络编程>30章详细介绍了几种服务器设计范式.总结了其中的几种,记录一下: 多进程的做法: 1.每次创建一个新的请求,fork一个子进程,处理该连接的数据传输. 2.预先派生一定 ...

  3. 基于内存,redis,mysql的高速游戏数据服务器设计架构

    转载请注明出处,欢迎大家批评指正 1.数据服务器详细设计 数据服务器在设计上采用三个层次的数据同步,实现玩家数据的高速获取和修改. 数据层次上分为:内存数据,redis数据,mysql数据 设计目的: ...

  4. [MySQL] 关系型数据库的设计范式 1NF 2NF 3NF BCNF

    一.缘由: 要做好DBA,就要更好地理解数据库设计范式.数据库范式总结概览: 为了更好地理解数据库的设计范式,这里借用一下知乎刘慰老师的解释,很通俗易懂.非常感谢!   二.具体说明: 首先要明白”范 ...

  5. H2Engine游戏服务器设计之属性管理器

    游戏服务器设计之属性管理器 游戏中角色拥有的属性值很多,运营多年的游戏,往往会有很多个成长线,每个属性都有可能被N个成长线模块增减数值.举例当角色戴上武器时候hp+100点,卸下武器时HP-100点, ...

  6. 游戏服务器设计之NPC系统

    游戏服务器设计之NPC系统 简介 NPC系统是游戏中非常重要的系统,设计的好坏很大程度上影响游戏的体验.NPC在游戏中有如下作用: 引导玩家体验游戏内容,一般游戏内有很多主线.支线任务,而任务的介绍. ...

  7. h2engine游戏服务器设计之聊天室示例

    游戏服务器设计之聊天室示例 简介 h2engine引擎建群以后,有热心网友向我反馈,想尝试h2engine但是没有服务器开发经验觉得无从入手,希望我能提供一个简单明了的示例.由于前一段时间工作实在忙碌 ...

  8. 《Java编程思想第四版》第 16 章 设计范式-提到观察者模式

    在由Gamma,Helm 和 Johnson 编著的<Design Patterns>一书中被定义成一个“里程碑”.那本书列出了解决这个问题的 23 种不同的方法 16.1.2 范式分类 ...

  9. 基于内存,redis,mysql的高速游戏数据服务器设计架构 ZT

    zt  http://www.cnblogs.com/captainl1993/p/4788236.html 1.数据服务器详细设计 数据服务器在设计上采用三个层次的数据同步,实现玩家数据的高速获取和 ...

随机推荐

  1. Leetcode228. Summary Ranges汇总区间

    给定一个无重复元素的有序整数数组,返回数组区间范围的汇总. 示例 1: 输入: [0,1,2,4,5,7] 输出: ["0->2","4->5",& ...

  2. Leetcode561.Array Partition I数组拆分1

    给定长度为 2n 的数组, 你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从1 到 n 的 min(ai, bi) 总和最大. 示例 ...

  3. TZ_15Spring-Cloud_Eureka-Ribbon-Hystix-Feign-Zuul微服务整合

    1.一个微服务框架的基本流程 2.Eureka                                   --Feign-Zuul Eureka:就是服务注册中心(可以是一个集群),对外暴露 ...

  4. WPF 的另类资源方式 Resources.resx

      类似Winform的搞法,可以把资源放到Resources.resx中. 1.字符串 打开这个编辑器后,输入Name和Value就可以了. CS代码里面,很简单的调用: var title = W ...

  5. 深入浅析python中的多进程、多线程、协程

    深入浅析python中的多进程.多线程.协程 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源 ...

  6. 关于python的列表操作(二):排序,统计

    # 列表操作 num_list = [2, 5, 8, 6, 7, 9, 5, 7] # 升序 num_list.sort() print(num_list) # 降序 num_list.sort(r ...

  7. 清除SQL数据库文本字段中的回车、换行符的方法

    清除SQL数据库中文本字段的回车.换行符的方法 清除回车符: update tableName set columnName = rtrim(ltrim(replace(columnName ,cha ...

  8. 利用SQL查询扶贫对象医保报销比率的审计方法

    利用SQL查询扶贫对象医保报销比率的审计方法 扶贫资金惠及贫困百姓的切身利益,主管部门多,资金实行逐级下拨,并且扶贫项目小而分散,主要在乡镇和农村实施.根据湖北省审计厅关于2017年扶贫审计工作方案的 ...

  9. Django 配置MySQL数据库 mysql

    Django 配置MySQL数据库 在settings.py中配置 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', # ...

  10. vue路由history模式刷新页面出现404问题

    vue hash模式下,URL中存在'#',用'history'模式就能解决这个问题.但是history模式会出现刷新页面后,页面出现404.解决的办法是用nginx配置一下.在nginx的配置文件中 ...