一:客户端

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

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. chage 修改用户密码有效期限的命令

    chage 用语法格式: chage [-l] [-m 最小天数] [-M 最大天数] [-W 警告] [-I 失效日] [-E 过期日] [-d 最后日] 用户 前面已经说的好多了,这个只是一笔带过 ...

  2. LA3177 Beijing Guards

    Beijing Guards Beijing was once surrounded by four rings of city walls: the Forbidden City Wall, the ...

  3. TZ_09_MyBatis的pageHelper

    1.分页操作使用MyBatis的PageHelper 1>导入pageHelper的坐标 <dependency> <groupId>com.github.pagehel ...

  4. 【笔记】LR中设置检查点

      我们为什么需要在LR中设置检查点?? 我们在录制编写脚本后,通常会进行回放,如果回放通过没有错误.我们就认为脚本是正确的.那么LR怎么区分脚本是否回放正确:基本上所有脚本回放错误都是因为 404错 ...

  5. mybatis深入理解(二)-----Mybatis数据源与连接池

    对于ORM框架而言,数据源的组织是一个非常重要的一部分,这直接影响到框架的性能问题.本文将通过对MyBatis框架的数据源结构进行详尽的分析,并且深入解析MyBatis的连接池.本文首先会讲述MyBa ...

  6. NSIS之MUI

    NSIS 2.0 版本支持定制的用户界面.所谓的 Modern UI(下称 MUI) 就是一种模仿最新的 Windows 界面风格的界面系统.MUI 改变了 NSIS 脚本的编写习惯,它使用 NSIS ...

  7. Mysql千万级访问量架构

    1.HTML 静态化 其实大家都知道,效率最高.消耗最小的就是纯静态化的html页面,所以我们尽可能是我们的网站上的页面采用静态页面来实现,这个最简单的方法其实也是最有效的方法.但是对于大量内容并且频 ...

  8. ORACLE时间常用函数(字段取年、月、日、季度)

    TO_DATE格式 Day: dd number 12 dy abbreviated fri day spelled out friday ddspth spelled out, ordinal tw ...

  9. tomcat9下载与安装

    tomcat9下载与安装 官网下载地址:https://tomcat.apache.org/ 百度云地址:链接:https://pan.baidu.com/s/109PYcSh-eqTctLAXIsb ...

  10. top进程命令

    top命令用来显示系统当前的进程状况. 格式:top [选项] 主要选项如下. d:指定更新的间隔,以秒计算. q:没有任何延迟的更新.如果使用者有超级用户,则top命令将会以最高的优先序执行. c: ...