UNP 客户/服务器 程序设计范式 笔记


1,TCP/IP协议概述


首先回顾一下TCP/IP模型,并知道各个层次在操作系统的哪一个层次; 



看上图,OSI模型的底下两层是随系统提供的设备驱动程序和网络硬件。通常情况下,除需知道数据链路的某些特性外,

我们不用关心这两层的情况。网络层由IPv4和IPv6两个协议处理,可以选择的传输层有TCP或UDP。OSI模型的顶上三层

被合并为一层,称为应用层,这就是web客户(浏览器)、telnet客户、web服务器等都所在的层。
   可见OSI模型以传输层为分界线;套接字编程接口为应用层进入传输层的接口;也符合抽象的目的:对应用层隐藏网络

协议的具体通信细节,应用层只处理具体网络应用;而底下四层(从传输层到物理层)对具体网络应用了解不多,却处理

所有的通信细节:发送数据,等待确认,给无序到达的数据排序,计算并验证校验和,等等。其次,顶上三层通常构成所

谓的用户进程,底下四层却通常作为操作系统内核的一部分提供。完全符合Unix和其他现代操作系统都提供分割用户进程

和内核的机制。


2, 基本TCP 套接字编程

  TCP:传输控制协议(Transmission Control Protocol).TCP是一个面向连接的协议,为用户进程提供可靠的全双工字节流。

TCP套接字是一种流套接字(stream socket). TCP关心确认,超时和重传之类的细节。大多数网络应用程序使用TCP;

TCP基本的客户\服务器模型如下图:


以Nginx为例它 工作在应用层,并监听某一个我们指定的一个端口,并通过进程池或线程池来接受客户端的连接,

这里有个“惊群”的概念,先来看看什么是“惊群”?简单说来,多线程/多进程(linux下线程进程也没多大区别)等

待同一个socket事件,当这个事件发生时,这些线程/进程被同时唤醒,就是惊群。可以想见,效率很低下,许多

进程被内核重新调度唤醒,同时去响应这一个事件,当然只有一个进程能处理事件成功,其他的进程在处理该事

件失败后重新休眠(也有其他选择)。这种性能浪费现象就是惊群。惊群通常发生在server 上,当父进程绑定一

个端口监听socket,然后fork出多个子进程,子进程们开始循环处理(比如accept)这个socket。每当用户发起一

个TCP连接时,多个子进程同时被唤醒,然后其中一个子进程accept新连接成功,余者皆失败,重新休眠。

nginx 解决惊群的方法,简单了说,就是同一时刻只允许一个nginx worker在自己的epoll中处理监听句柄(这里采用

互斥锁机制,获得accept锁,多个worker仅有一个可以得到这把锁。)


3,探讨现代流行的应对高并发请求网络服务端设计架构;


什么是客户/服务器程序设计范式?简单点儿说就是在写客户/服务器相关程序时的一些特定的设计方式,将这些设计方式总结起来,形成几种固定

的模式,就叫客户/服务器程序设计范式.这里叫客户/服务器程序设计范式,其实大部分讲的是服务器程序设计范式,而客户程序的编写通常比服务

器程序容易一些,因为客户中进程控制要少的多.尽管如此,我们还是会提及编写客户程序的各种方式.

从总体上来说服务器设计范式分为循环设计和并发设计两种,也就是我们经常说的“迭代服务器”和“并发服务器”.
迭代服务器程序是最简单,也是最容易编写的,但这种类型适用情形极为有限,因为这样的服务器在完成对当前客户的服务之前无法处理已等待服务的新客户.
并发服务器实现起来有很多种方式:

  1. 1为每个客户调用fork派生一个子进程的方式(据说apache1.0就是这么干的);
  2. 预先派生子进程的方式(apache里的prework模块就采用这种方式);
  3. 为每个客户请求创建一个线程的方式;
  4. 预先创建线程的方式;
  5. 当然还有多进程、多线程混合模型(apache中的worker模块采用这种方式);
  6. 还有一种比较特殊的方式,就使在单个进程中使用select处理任意多个客户请求等等。。。

而客户程序设计在大多数情况下迭代的方式就能够满足需求,虽然它是以停-等方式运作.而在一些特殊情况下,还是要有一些变通

方式.比如,客户程序阻塞在用户输入其间,对网络连接相关事件是看不到的.此时可以用select使得进程在等待用户输入其间能够得到网络事件通知;也可以用非阻塞I/O来实现。当然客户程序也可以用fork派生子进程,或者用多线程来实现并发。

在下面我们会对各种服务器设计范式进行测试,并讨论这些范式之间的差异。(UNPv1)主要包括以下几种范式:

  1. 迭代服务器程序《客户/服务器程序设计范式---迭代服务器程序》
  2. 并发服务器程序,每个客户一个子进程《客户/服务器程序设计范式--并发服务器程序,每个客户一个子进程》
  3. 预先派生子进程服务器程序,accept无上锁保护《客户/服务器程序设计范式--并发服务器程序,accept无上锁保护》
  4. 预先派生子进程服务器程序,accept使用文件锁保护《客户/服务器程序设计范式--并发服务器程序,accept使用文件上锁保护》
  5. 预先派生子进程服务器程序,accept使用线程锁保护《客户/服务器程序设计范式--并发服务器程序,accept使用线程上锁保护》
  6. 预先派生子进程服务器程序,传递描述字<《客户/服务器程序设计范式--并发服务器程序,传递描述符》
  7. 并发服务器程序,每个客户一个线程
  8. 预先创建线程服务器程序,每个线程各自accept
  9. 预先创建线程服务器程序,主线程统一accept

我们将针对每个服务器程序运行同一客户程序的多个实例,以测量服务固定数目请求所需的CPU时间。注意,这里的时间指的是用于进程控制所需的CPU时间,而迭代服务器是我们的基准,因为迭代服务器没有进程控制开销。所以从其他服务器的实际CPU时间中扣除迭代服务器的实际CPU时间就得到相应服务器用于进程控制所需的时间。(比较结果见UNP第30章插图)

经过比较我们可以得出以下几点总结性意见:

1,当系统负载较轻时,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就够了。

2,相比传统的每个客户fork一次设计范式,预先创建一个子进程池或子线程池的设计范式能够把进程控制

   CPU时间降低10倍或以上。

3,某些实现允许多个子进程或线程阻塞在同一个accept调用中,另一些实现却要求包绕accept调用安置某种

 类型的锁加以保护。文件上锁或Pthread互斥锁都可以。

4,让所有子进程或线程自行调用accept通常比让父进程或主线程独自调用accept并把描述符字传递给子进程或

   线程来得简单而快速。

5,由于潜在select冲突的原因,让所有子进程或线程阻塞在同一个accept调用中比让他们阻塞在同一个select调用

   中更为可取。

6,使用线程通常远快于使用进程。不过选择每个客户一个子进程还是每个客户一个线程取决于操作系统提供什么支持,

   还可能取决于为服务每个客户连接的服务器调用fork和exec,那么fork一个单线程的进程可能快于fork一个多线程

   的进程。


参考:http://blog.chinaunix.net/uid-20788107-id-485103.html


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值