服务器端程序设计范式
1,迭代服务器
完全处理完某个客户程序才进行下一个客户
1 | for ( ; ; ) { |
2,并发服务器
2.1,为每个客户请求fork一个进程
1 | for ( ; ; ) { |
2.2,为每个客户请求创建一个线程
1 | for ( ; ; ) { |
3,预先派生子进程
3.1,每个子进程调用accept,无上锁
注意惊群效应:一个客户请求会唤醒所有子进程
多个进程在引用同一个监听套接字的描述符上调用accept,只适用于Berkeley的内核,要其他内核就用上锁保护accept,使其每次只能一个进程调用accept
1 | /* include serv02 */ |
3.2,文件上锁保护accept
1 | 见书P659 |
3.3,线程互斥锁保护accept
1 |
|
3.4,父进程向子进程传递套接字描述符
绕过为所有子进程的accept调用提供上锁保护的可能需求,不过需要从父进程到子进程的某种形式的描述符传递
1 | 见书P663 |
4,预先创建线程
4.1,以互斥锁上锁的方式保护accept
1 | /* include serv07 */ |
4.2,由主线程调用accept
在程序启动阶段创建一个线程池之后只让主线程调用accept并把每个客户连接传递给池中某个可用线程,这一点类似描述符传递的版本。
1 | /* include serv08 */ |
总结:
1,当系统负载较轻时,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就够了。这个模型可以与inetd结合使用,也就是inetd处理每个连接的接受。接下来的意见是针对重负荷服务器而言的。
2,相对传统的每个客户fork一次设计范式,预先创建一个子进程池或一个线程池的设计范式能够把进程控制CPU时间降低10倍以上。上面的例子还可以优化:监视闲置子进程的个数,随着服务客户数的动态变化而增加或减少。
3,某些实现允许多个子进程或线程阻塞在同一个accept调用中,另一些实现却要求包绕accept调用设置某种类型的锁加以保护。文件上锁或Pthread互斥锁都可以使用。
4,让所有子进程或线程自行调用accept通常比让父进程或主线程独自调用accept并把描述符传递给子进程或线程来的简单快捷。
5,由于潜在select冲突的原因,让所有子进程或线程阻塞在同一个accept调用中比让他们阻塞在同一个select调用中更可取。
6,使用线程比通常比使用进程快。不过还是取决于系统,比如accept客户连接的服务器调用fork和exec,那么fork一个单线程的进程比fork一个多线程的进程快。