首页 > C/C++ > 从头编写高性能服务程序8-多进程非阻塞epoll-prefork

从头编写高性能服务程序8-多进程非阻塞epoll-prefork

上一个版本在accept的位置仍旧会被阻塞
这样当一个链接进来的时候就会产生一个新的进程
进程的不断开启和释放会造成很大的性能影响
而一般Apache和Nginx的做法就是先产生N个进程用以备用
当有实际链接的时候,就由这些进程获取并进行处理
(注:Nginx的线程模式只在Windows下有效,在Linux下是使用进程模型的)
这样我们就有两个地方需要改造

第一个是将listen端口也变为非阻塞
这样当有新链接进来的时候我们得到通知才去处理
这样accept调用就不会被阻塞在进程导致进程无法进行后续的工作

第二是进程一启动之后就fork N个进程
这些进程启动之后就自行获取各自的accept
然后自行处理各自获取的链接并管理其生命周期内的所有内容

将listen也放置到epoll中
就需要在每次获得epoll events的时候判断下
这个events是前面放进去的listen,如果是listen就要accept
如果是accept的就进行数据传输处理

下载: code.txt
  1. #include <sys/socket.h>
  2. #include <netinet/in.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <fcntl.h>
  7. #include <sys/epoll.h>
  8. int main(){
  9.     int listen_fd,accept_fd,flag;
  10.     struct sockaddr_in my_addr,remote_addr;
  11.     if ((listen_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
  12.         perror("create socket error");
  13.         exit(1);
  14.     }
  15.     if (setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR
  16.                     ,(char *)&flag,sizeof(flag)) == -1){
  17.         perror("setsockopt error");
  18.     }
  19.     int flags = fcntl(listen_fd, F_GETFL, 0);
  20.     fcntl(listen_fd, F_SETFL, flags|O_NONBLOCK);
  21.     my_addr.sin_family = AF_INET;
  22.     my_addr.sin_port = htons(3389);
  23.     my_addr.sin_addr.s_addr = INADDR_ANY;
  24.     bzero(&(my_addr.sin_zero), 8);
  25.     if (bind(listen_fd, (struct sockaddr *)&my_addr,
  26.                 sizeof(struct sockaddr_in)) == -1) {
  27.         perror("bind error");
  28.         exit(1);
  29.     }
  30.     if (listen(listen_fd,1) == -1){
  31.         perror("listen error");
  32.         exit(1);
  33.     }
  34.     int pid=-1;
  35.     int addr_len = sizeof(struct sockaddr_in);
  36.     int max_process_num = 3;
  37.     int child_id;
  38.     int i;
  39.     int child_process_status;
  40.     for(i = 0; i < max_process_num; i++){
  41.         if(pid != 0){
  42.             pid = fork();
  43.         }
  44.         else{
  45.             child_id = i;
  46.         }
  47.     }
  48.     if(pid == 0){
  49.         int accept_handles = 0;
  50.         struct epoll_event ev,events[20];
  51.         int epfd = epoll_create(256);
  52.         int ev_s = 0;
  53.         ev.data.fd = listen_fd;
  54.         ev.events = EPOLLIN|EPOLLET;
  55.         epoll_ctl(epfd,EPOLL_CTL_ADD,listen_fd,&ev);
  56.         for(;;){
  57.             ev_s = epoll_wait( epfd,events, 20, 500 );
  58.             int i = 0;
  59.             for(i = 0; i<ev_s; i++){
  60.                 if(events[i].data.fd == listen_fd){
  61.                     int max_process_accept = 3;
  62.                     if(accept_handles < max_process_accept){
  63.                         accept_handles++;
  64.                         int addr_len = sizeof( struct sockaddr_in );
  65.                         accept_fd = accept( listen_fd,
  66.                                                 (struct sockaddr *)&remote_addr, &addr_len );
  67.                         int flags = fcntl(accept_fd, F_GETFL, 0);
  68.                         fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
  69.                         ev.data.fd = accept_fd;
  70.                         ev.events = EPOLLIN|EPOLLET;
  71.                         epoll_ctl(epfd,EPOLL_CTL_ADD,accept_fd,&ev);
  72.                         printf("Child:%d,ProcessID:%d,EPOLLIN,fd:%d,accept:%d\n", child_id, getpid(), listen_fd, accept_fd);
  73.                     }
  74.                 }
  75.                 else if(events[i].events&EPOLLIN){
  76.                     char in_buf[1024];
  77.                     memset(in_buf, 0, 1024);
  78.                     int recv_num = recv( events[i].data.fd, &in_buf, 1024, 0 );
  79.                     if( recv_num ==0 ){
  80.                         close(events[i].data.fd);
  81.                         accept_handles--;
  82.                         printf("Child:%d,ProcessID:%d,EPOLLIN,fd:%d,closed\n", child_id, getpid(), events[i].data.fd);
  83.                     }
  84.                     else{
  85.                         printf("Child:%d,ProcessID:%d,EPOLLIN,fd:%d,recv:%s\n", child_id, getpid(), events[i].data.fd, in_buf);
  86.                     }
  87.                 }
  88.             }
  89.         }
  90.     }
  91.     else{
  92.         //manager the process
  93.         wait(&child_process_status);
  94.     }
  95.    
  96.     return 0;
  97. }
分类: C/C++ 标签:
  1. 2010年5月7日21:57 | #1

    楼主你好,我最近也在写HTTP服务器的程序,看了你的这系列的文章,写得很仔细。
    这篇的多进程,我有个不太清楚的地方,这里的多个进程都是使用同一个listen_fd, 那么当多个进程都在epoll的时候,若有请求发生,会是哪一个进程得到该请求的事件,进而处理呢?

  2. 代码罐头
    2010年5月8日16:50 | #2

    这个问题就是多进程里面的群惊现象
    当所有进程都阻塞在listen端口时
    一旦系统获得listen端口的状态变化
    会把所有的线程都唤醒,而只有一个获取后进行处理
    其他的在被阻塞后继续沉睡
    不过在Linux 2.4以后这个群惊的现象已经被内核给处理掉了
    实际测试下来只有一个进程会被再次唤醒,而其他保持阻塞,
    这样阻塞在listen端口其实效率不比阻塞在同享锁差.毕竟调度是在内核级别的.

  3. 2010年5月11日18:04 | #3

    非常感谢回复,抱歉查看晚了。
    现在处于初学阶段,看的还不是很明白。
    可以这样理解么,具体唤醒哪一个进程是由内核去处理,
    不需要自己判断处理?如果这样的话,内核是用什么机制去判断的呢?

  4. keily
    2010年8月9日13:42 | #4

    你好!谢谢分享!
    以后能不能多写点注释呢?谢谢!

  1. 本文目前尚无任何 trackbacks 和 pingbacks.