网络编程——客户/服务器程序设计范式

网络编程
网络编程
  • 咱们在须要开发一个服务器程序时,有较多的的程序设计范式可供选择,不一样范式有其自身的特色和实用范围,明了不一样范式的特性有助于咱们服务器程序的开发。
  • 客户端程序一般比服务器容易些,固然客户端程序也可使用这些程序设计范式,由于它们蕴含的道理都是相通的。
  • 本文所设计的服务器主要是指基于TCP的服务器

迭代服务器

迭代TCP服务器 老是在彻底处理某个客户的请求以后才开始下一个客户的请求处理
这样的服务器实际中比较少见。
基于UDP的大多服务器倒是这样实现编程

并发服务器,每一个客户请求fork一个子进程

传统并发服务器 调用fork派生一个子进程来处理每一个客户,这使得服务器可以同时为多个客户服务,每一个进程一个客户
客户数目的惟一限制是操做系统对其可以同时拥有多少子进程的限制。
绝大多数TCP服务器程序都是按这个范式编写。
并发服务器的问题在于为每一个客户现场fork一个子进程比较耗费CPU时间。服务器

预先派生子进程,每一个子进程无保护地调用accept

不一样于传统意义的并发服务器那样为每一个客户现场派生一个子进程,而是 在启动阶段预先派生必定数量的子进程,当有客户链接到达时,这些子进程就能当即为它提供服务
这种技术的有点在于无需引入父进程执行fork的开销就能处理新到来的客户。缺点是父进程必须在服务启动阶段猜想须要预先派生多少子进程。若是某个时刻客户数刚好等于子进程总数,那么新到的客户将被忽略,直到至少有一个子进程完成处理从新可用。网络

预先派生子进程,使用文件上锁保护accept

在多个进程中引用同一个监听套接字的描述符上调用accept的作法在某些系统实现是不被支持的,那么解决办法是让应用进程在调用accept先后安装某种形式的锁(lock),这样任意时刻只有一个子进程阻塞在accept调用中,其余子进程则阻塞在获取保护accept的锁上。
这里使用文件锁来保护,文件锁涉及到文件系统的操做,可能比较耗时。多线程

预先派生子进程,使用线程互斥锁上锁保护accept

相比于 预先派生子进程,使用文件上锁保护accept,使用线程锁保护accept,这种方法 不只适用于同一进程内各个线程间的锁保护,并且可以用于不一样进程之间的锁保护
在不一样进程间的锁保护须要注意的是并发

  • 互斥锁变量必须存放在由全部进程共享的内存区中
  • 必须告知线程函数库这个锁是用于不一样进程间共享的互斥锁

预先派生子进程,父进程向子进程传递套接字描述符

只让父进程嗲用accept,而后把所接受的已经链接的套接字 传递 给某个子进程。
这样绕过了为全部子进程的accept调用提供上锁保护的需求,可是须要从父进程到子进程进行某种形式的描述符传递。
这种技术会上代码比较复杂,父进程必须跟踪子进程的闲忙状态,以便于给空闲的子进程传递新的套接字。app

并发服务器,每一个客户端请求建立一个线程

相比于多进程模型,若是服务器主机提供支持线程,咱们能够改用线程以取代进程。线程相比于进程的优点这里再也不累述。函数

预先建立线程服务器,使用互斥锁上锁保护accept

相比预先派生一个子进程池快于为每一个客户线程fork一个子进程池相似的道理,在有线程支持的系统上,预先建立的线程池取代为每一个客户现场建立一个线程的作法有相似的性能提高。
这种模式的基本设计是预先建立一个线程,并让每一个线程各自调用accept,取代让每一个线程都阻塞在accept调用中的作法,使用互斥锁保证任什么时候刻只有一个线程在调用accept。性能

预先建立线程服务器,由主线程调用accept

程序启动阶段建立一个线程池后让主线程调用accept;
主线程把每一个客户链接传递给池中某个可用的线程,相似于进程版本的作法。
这样的设计问题在于主线程如何将一个已链接套接字传递给线程池中某个可用线程spa

咱们有不少实现手段,本可用如前面同样使用描述符传递,可是既然全部线程和全部描述符都在同一个进程中,那么也就没有必要把一个描述符从一个线程传递到另外一个线程。接收线程只须要知道这个已链接套接字描述符的值(传递描述符可不仅是传递这个值,事实上是须要传递这个套接字的引用,所以也将返回一个不一样于原值的描述符,该套接字的引用计数也会增长操作系统

总结

  • 系统负载较轻时,每来一个客户请求现场派生一个子进程为之服务的传统并发服务器程序模型就足够了
  • 相比传统的每一个客户fork一次设计范式,预先建立一个子进程池或一个线程池的范式可以把进程控制CPU时间下降10倍或以上。编写这些范式的程序并不会复杂,不过会有额外的工做,好比监视如今子进程数,随着所服务客户数的动态变化而增长或减小这个数目
  • 某些实现容许多个子进程或线程阻塞在同一个accept调用中,另外的实现却要求对accept调用须要某种类型的锁加以保(文件锁或者互斥锁等)
  • 让全部子进程或线程自行调用accept一般比让父进程或主线程独自调用accept并把描述符传递个子进程或线程来的简单和快捷
  • 因为潜在select冲突的缘由,让全部子进程或线程阻塞在同一accept调用中比让他们阻塞在同一个select调用中更可取。
  • 使用 线程一般远快于使用进程,不过选择每一个客户一个子进程仍是每一个客户一个线程取决于操做系统提供什么支持(某些系统不提供线程支持),还可能取决于为服务每一个客户须要激活其余什么程序。例如,若是accept客户链接的服务器调用fork和exec,那么fork一个单线程的进程可能快于fork一个多线程的进程,另外还有资源等方面的综合考虑。
相关文章
相关标签/搜索