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

unix 网络编程第30章读书笔记, 这里只记录大致实现方式, 具体代码实现还请阅读此书

TCP 迭代服务器

完全同步方式, 完全处理某个客户的请求之后才专向下一个客户,优点是代码简单,并且没有进程控制所需的时间

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

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

TCP 预先派生子进程服务器程序, accept 无上锁保护

上一个方式的增强版, 使用preforking 的技术。 使用该技术的服务器在启动阶段预先派生一定数量的子进程,
当各个客户链接到达的时候,这些子进程能够立即为它们进行服务。

优点是无需引入父进程执行fork的开销就能处理新的客户, 缺点是父进程必须在服务器启动阶段猜测预先派生多少
子进程。 如果某个时刻客户数恰好等于子进程总数, 那么新到的客户将被忽略, 直到至少有一个子进程重新可用
但是这些客户并没有被完全忽略, 内核将为每个新到的客户完成三次握手, 直到达到相应的套接字上listen 调用的
backlog(已建立连接但是尚未被accept 返回,固定大小的缓冲区)数为止, 当有进程调用accept的时候,将
backlog里面的链接返回。这么一来客户就能觉察到服务器在相应时间上的恶化, 尽管调用connect可以立即返回,
但是它的第一个请求可能会在一段时间之后才被服务器处理

具体的实现方式,是将 主socketfd 在创建子进程之前创建出来, 这样fork的时候就会自动复制 socketfd,(其实就是增加socket上的引用计数)
每个进程都对这个socketfd 调用accept。 通过这种方式来多进程同时监听一个端口。

但是这有一个问题,就是当某个链接进来,所有N个子进程都会被唤醒, 因为所有N个子进程所用的监听描述符(他们有相同的值)指向同一个socket结构
致使他们在同一个等待通道(wait channel)即这个socket结构的so_timeo 成员上进入睡眠, 尽管所有N个子进程均会被唤醒, 其中只有最先运行的子进程
获得那个客户链接,其余N-1个子进程继续恢复睡眠

TCP预先派生子进程服务器程序, accept使用文件上锁保护

让应用进程在调用accept 前后安置某种形式的锁, 这样任意时刻只有一个子进程阻塞在accept调用中, 其他子进程则阻塞在试图获取用于保护accept的锁上

在指定的目录创建一个文件,并且立即unlink掉。 这样,通过从文件目录中删除该路径名, 以后即使程序崩溃,这个临时文件也会完全消失。
然而只要有一个或多个进程打开着这个文件(引用计数大于0), 该文件本身就不会被删除

上锁通过调用 fcntl(lock_fd, F_SETLKW, &lock_it)
解锁 通过使用 fcntl(lock_fd, F_SET, &unlock_it)

TCP 预先派生子进程服务器程序, accept 使用 线程上锁保护

这种方法不涉及文件系统的操作(比上一中方法省时),并且不仅适用于 同一进程内各线程之间的上锁, 而且适用于不同进程之间的上锁

需要满足以下两个需求

  • 互斥锁变量必须存放在由所有进程共享的内存区中
  • 必须告知线程库函数这是在不同进程之间共享的内存锁
TCP 预先派生子进程服务器程序, 传递描述符

对预先派生子进程服务器程序的最后一个修改版本是只让父进程调用accept, 然后把所接受的已连接套接字传递给某个子进程
这么做是为了绕过为所有子进程上锁的需求,不过从父进程传递给子进程描述符,会使得代码多少有写复杂, 因为父进程必须跟踪
子进程的忙闲状态, 以便给空闲子进程传递新的套接字

  • 使用 socketpair 建立进程间通道
  • 父进程必须处理监听套接字以及所有的字节流套接字(与子进程的)(使用select
TCP 并发服务器程序, 每个客户一个线程

使用子线程来代替子进程

TCP预先创建线程服务器程序, 每个线程各自accept

创建子线程线程池, 使用互斥锁(这里没有必要使用文件上锁的方式, 互斥锁足够)

TCP 预先 创建线程服务器程序, 主线程统一accept

这个的主要问题同使用进程一样, 就是如何把已连接的socketfd传递到子线程,这里就没有必要使用上面的方式了
因为是在同一个进程内, 我们可以通过共享 数组的方式将socket分配给各个线程

总结:
  • 当系统负载较轻的时候, 没来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型足够了
    这个模型甚至可以与inetd结合使用, 也就是inetd处理每个链接的请求

  • 相比传统的每个客户fork一次的设计范式, 预先创建一个子进程池或者是一个线程池能够把控制CPU时间降低
    10倍或以上, 编写这些范式的程序并不复杂, 主要是需要监控闲置子进程个数, 控制子进程的创建与回收

  • 让所有子进程或者线程自行调用accept通常比让父进程或者主线程独自调用accept 并把 fd传递给子进程要来得简单

  • 使用线程通常远快于使用进程,不过选择每个客户一个子进程还是每个客户一个线程取决于操作系统提供什么支持,还可能取决于为服务每个
    客户需要激活其他什么程序

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值