客户/服务器程序设计模板(一)服务器

来源:互联网 发布:京东小程序源码 编辑:程序博客网 时间:2024/04/30 08:34

一 Unix服务器程序可选的进程控制类型:

(1)迭代服务器:

*适用情形极为有限,因为这样的服务器在完成当前客户的服务之前无法处理已等待服务的新客户。

(2)多进程并发服务器:

*为每个客户fork一个子进程,传统上大多数Unix服务器程序属于这种类型。

(3)select/poll/epoll+单个进程的服务器:

(4)多线程并发服务器:

*为每个客户创建一个线程。

(5)进程池:预先派生子进程,让服务器在启动阶段调用fork创建一个子进程池,当各个客户连接到达时,这些子进程立即就为它们服务。

1.优点:无须引入父进程执行fork的开销就能处理新到的客户。

2.缺点:父进程必须在服务器启动阶段猜测需要派生多少子进程。

*如果某个时刻客户数恰好等于子进程总数,那么新到的客户将被忽略(并未完全忽略),直到一个子进程重新可用。

*内核将为每个新到的客户完成三次握手连接,直到到达listen调用的backlog数为止,然后在服务器上调用accept时把这些以完成的连接传递给它。

*这样客户就能察觉到服务器在相应时间上的恶化,因为尽管它的connect调用立即返回,但是它的第一个请求可能是在一段时间之后才被服务器处理。

3.服务器应对客户负载的变动方法:

*父进程必须做的就是持续监视可用子进程数目,一旦降到某个阀值就派生额外的子进程。

*一旦超过另一个阀值就终止一些过剩的子进程,过多可用的子进程也会导致性能退化(惊群(thundering herd)问题会更严重)。

*惊群:只有一个进程获得连接,但是所有子进程都被唤醒(就TCP而言)。

(6)线程池:预先派生子线程,让服务器在启动阶段创建一个线程池。

(7)事件驱动模型(libev和libevent库)。


二 多进程/线程并发服务器:每个客户一个进程/线程。

(一)传统的多进程并发服务器:(不使用进程池)

(1)客户数目的唯一限制:操作系统对以其名义运行服务器的用户ID能够同时拥有多少子进程的限制。

(2)问题:为每个客户现场fork一个子进程比较耗费CPU的时间。

(二)使用进程池的并发服务器(一):每个子进程各自调用accept。

(1)accept无上锁保护:每个子进程在同一个监听套接字上调用accept返回一个已连接套接字。

*仅适用于Berkeley的内核,System V内核不允许这么做。

(2)accept使用文件上锁保护或互斥锁上锁保护:

*任意时刻只有一个子进程在阻塞在accept调用中,其他子进程则阻塞于保护accept的锁上。

(3)select冲突:

*当多个进程在引用同一个套接字的描述符上调用select就会发生冲突,内核必须唤醒阻塞在select调用中的所有进程。

*如果多个进程阻塞在引用同一个实体(套接字或普通文件)的描述符上,那么最好直接阻塞在accept之类的函数而不是select上。

(三)使用进程池的并发服务器(二):只让父进程调用accept,然后把所接受的已连接套接字"传递"给某个子进程。

*避免了所有子进程的accept调用提供上锁保护的需求,但是需要从父进程到子进程的某种形式(Unix域字节流套接字)的描述符传递。

*缺点:父进程通过字节流管道把描述符传递到各个子进程,并且各个子进程通过字节流管道写回单个字节,无论是使用共享内存区中的互斥锁,还是文件锁相比,都更费时。

(四)传统多线程并发服务器:每个客户一个线程。

(五)使用线程池的并发服务器(一):每个线程各自调用accept。

(1)利用互斥锁保证任何时刻只有一个线程阻塞在accept调用上。

(六)使用线程池的并发服务器(二):主线程调用accept。


三 web server:


四 总结:

(一)类型:

(1)迭代服务器。

(2)传统并发服务器:

*每个客户fork一个子进程。

*每个客户创建一个线程。

(3)进程池:

*每个子进程无保护阻塞在accept上。

*使用文件上锁保护accept。

*使用互斥锁上锁保护accept。

*父进程调用accept,然后把所接受的已连接套接字描述符"传递"给某个子进程。

(4)线程池:

*使用互斥锁保护accept。

*主线程调用accept。


(二)建议(TCP服务器为例):

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

(2)相比传统的现场派生线程或进程,预先创建一个线程池或进程池能够大大提高效率。编写代码需注意:监视限制子进程或线程个数,随着所服务客户数的动态变化而增加或减少数目。

(3)使用线程通常远快于使用进程。

(4)让所有子进程或线程自行调用accept,通常比父进程或主线程独自调用accept并把描述符传递给子进程或线程来简单且快速。

(5)某些实现运行多个子进程或线程阻塞在同一个accept调用上,而另外一些实现不允许,可以使用文件上锁或互斥锁保证任意时刻只有一个子进程在阻塞在accept调用中,其他子进程则阻塞于保护accept的锁上。

(6)由于潜在的select冲突,让所有子进程或线程阻塞在同一个accept调用上比让它们阻塞在select调用中更可取。










原创粉丝点击