单/多进程模式/多进程池(prefork)模型

http://www.cnblogs.com/wully/archive/2011/12/17/forksserver.html

服务器的基本模型,不知道这么扯对不对,其实就是linux下服务器和客户端的通信模式。也就是面对客户端如狼似渴的访问需求,服务器该如何快速的响应。

我总结下来有这么几种:

  • 单进程提供服务
  • 多进程提供服务
  • 多进程池服务(prefork)
  • io复用提供服务(select,poll)
  • epoll(其实也是一种IO复用)
  • 多线程提供服务
  • 多线程池提供服务
  • 信号驱动提供服务

一一按照自己的想法写出来,还想和大家后续一起探讨下非阻塞io,异步io,共享内存,进程间通信等服务器常用技术。废话不说,直接开始。

 

单进程提供服务

这种模式只存在于我们的学习中,一个客户端请求由服务器响应后,这个客户完全占有了服务器,这回如何再来一个新客户,他必须等待服务器伺候完现有的这个客户。伺候不完,服务器是不会为新客户提供服务的,这个就是完全占有。

服务器和客户端的行为:

server : bind -> listen -> accept one request -> do request, send response -> close accept fd  -> accept next request ....

client  :  connect -> send request  -> wait response -> recieve response -> close connect

 

服务器的行为,一般是创建一个socket,然后把相关的端口,ip使用bind捆绑到socket上,通过listen监听该端口,当有客户请求到达时,处理请求,给出回应。处理完一个客户请求后,关闭该请求,在处理下一个。

客户端的行为,通过connect连接到server上去,发送请求,等待回应,收到答复,关闭连接。

这种方式的弊端,显而易见,在一个客户请求未处理完毕时,另一个客户必须等待,直到被accept。在web服务这种高并发请求中,这种服务器模型显然不行。

有一个参数这里我一直觉得很诡异,就是listen的第二个参数 backlog ,按照说明 这个参数是在建立三次握手中的连接数和完成网络连接但尚未被accept的连接数的和的最大值,但貌似在各个内核中实现又有所差异。在自己的本中实现了下,貌似超过了还会connect ok 。 

贴上我实验用的server代码,为了节约代码量,所有的错误处理均被忽略

 

?
/*
  * auther : wully_happy@163.com
  */
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/wait.h>
#include<string.h>
#include<time.h>
#define MAX_CONNECTION 2
 
int  main( int  argc, char ** argv){
     int  fd;
     time_t  ticks;
     int  port = 99999;
     fd = socket(AF_INET,SOCK_STREAM,0);
     struct  sockaddr_in addr;
     memset (&addr,0, sizeof (addr));
     addr.sin_family = AF_INET;
     addr.sin_port = htons(port);
     addr.sin_addr.s_addr = htonl(INADDR_ANY);
     int  size = sizeof ( struct  sockaddr);
     bind(fd,( struct  sockaddr*)&addr,size);
     listen(fd,MAX_CONNECTION);
     struct  sockaddr_in client_addr;
     while  (1){
         memset (&client_addr, 0, sizeof  (client_addr));
         char  buf[1024];
         memset (&buf,0, sizeof (buf));
         int  client_fd = accept(fd, ( struct  sockaddr *) &client_addr, &size);
         ticks = time  (0);
         snprintf(buf, sizeof (buf), "%s" , ctime (&ticks));
         write(client_fd, buf, sizeof (buf));
         sleep(3600);
         close(client_fd);
     }
     close(fd);
     return  0;
}

  可以用  telnet 127.0.0.1 99999 测试,可以看到但一个客户端使用telnet请求时,下一个必须等待。

 

多进程提供服务

 既然一个进程提供服务已经应付不过来,不如多生几个儿子来处理请求,作为老子只管对儿子进行监控就好。多进程应该就是这么个道理。每个请求都fork一个子进程来处理。

这个模式相对于第一种的优点就是 可以对多个请求进行处理,响应及时。

缺点就是每次请求都要生成一个新进程,处理完毕,还要销毁。成本有些高,在并发请求较高的时候,会把cpu耗尽。毕竟进程这个东西还是稍微有些重的东西。

 

修改程序很简单 ,在 accept后面插入代码即可,插入的代码为

?
int  client_fd = accept(fd, ( struct  sockaddr *) &client_addr, &size);
 
pid = fork();
if (pid > 0){
     close(client_fd);
     continue ;
}
close(fd);
ticks = time  (0);

 还缺少一步,就是防止子进程成为僵尸进程,要对信号SIGCHLD进行处理,使其在接到该信号后调用waitpid函数 回收子进程。

 

多进程池的服务 

 每次请求都生成新进程其实必要性并不大,大部分并发服务器处理的每秒并发量一般最多就在几百左右,因此一般几个或者十几个进程循环提供服务就可以hold住,为了减少每次请求建立新进程的成本,我们的前辈又发明了多进程池(prefork)的模式,预先生成若干进程来处理请求。

见过两种多进程池的实现,一种是父进程只管listen,子进程对每个请求accept。另一种是父进程负责accept,然后把accept后得到的confd句柄传递给子进程。

这里我先说下第一种实现,关于第二种的实现我会在select模式中来说,原因就是第二种模式的实现配合select的效果更佳。

 

第一种的测试代码为:(这里没有添加错误的处理程序)

?
/*
  * auther : wully_happy@163.com
  */
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/wait.h>
#include<string.h>
#include<time.h>
#define MAX_CONNECTION 2
 
int  main( int  argc, char ** argv){
     int  fd = -1;
     time_t  ticks;
     pid_t pid;
     pid_t pids[10];
     int  port = 99999;
     fd = socket(AF_INET,SOCK_STREAM,0);
     struct  sockaddr_in addr;
     memset (&addr,0, sizeof (addr));
     addr.sin_family = AF_INET;
     addr.sin_port = htons(port);
     addr.sin_addr.s_addr = htonl(INADDR_ANY);
     int  size = sizeof ( struct  sockaddr);
     bind(fd,( struct  sockaddr*)&addr,size);
     listen(fd,MAX_CONNECTION);
 
     int  i;
     for (i = 0; i< 10;i++){
         pid = fork();
         if (pid > 0){
             continue ;
         }
         pids[i] = pid;
         struct  sockaddr_in client_addr;
         while  (1){
             memset (&client_addr, 0, sizeof  (client_addr));
             char  buf[1024];
             memset (&buf,0, sizeof (buf));
             printf ( "wait\n" );
             int  client_fd = accept(fd, ( struct  sockaddr *) &client_addr, &size);
 
             close(fd);
             ticks = time  (0);
             snprintf(buf, sizeof (buf), "%s" , ctime (&ticks));
             write(client_fd, buf, sizeof (buf));
             sleep(3600);
             close(client_fd);
         }
     }
     close(fd);
     for (i = 0; i< 10;i++){
         int  status;
         if (pids[i] < 0){
             continue ;
         }
         waitpid(pids[i],&status,0);
     }
     return  0;
}

  

这里在原来有一个比较纠结的地方,就是accept,原来的linux版本会有惊群现象,也就是当一个请求到来时,多个子进程同时在accept阻塞中被唤醒,导致资源消耗过大,这个比较纠结的问题在现在的较新的linux内核中已经解决,另外一个纠结的问题时select的冲突,这个咱们在select中再续。先说到这里把。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值