多进程服务器中,epoll的创建应该在创建子进程之后

看我的测试代码,似乎应该是在创建子进程之后创建epoll的fd,否则程序将会有问题,试将代码中两个CreateWorker函数的调用位置分别调用,一个在创建epoll fd之前,一个在之后,在调用在创建之前的代码会出问题,在我的机器上(linux内核2.6.26)表现的症状就是所有进程的epoll_wait函数返回0, 而客户端似乎被阻塞了:

服务器端:

#include  < iostream >
#include 
< sys / socket.h >
#include 
< sys / epoll.h >
#include 
< netinet / in .h >
#include 
< arpa / inet.h >
#include 
< fcntl.h >
#include 
< unistd.h >
#include 
< stdio.h >
#include 
< errno.h >
#include 
< sys / types.h >
#include 
< sys / wait.h >

using   namespace  std;

#define  MAXLINE 5
#define  OPEN_MAX 100
#define  LISTENQ 20
#define  SERV_PORT 5000
#define  INFTIM 1000

typedef 
struct  task_t
{
    
int  fd;
    
char  buffer[ 100 ];
    
int  n;
}task_t;

int  CreateWorker( int  nWorker)
{
    
if  ( 0   <  nWorker)
    {
        
bool  bIsChild;
        pid_t nPid;

        
while  ( ! bIsChild)
        {
            
if  ( 0   <  nWorker)
            {
                nPid 
=  ::fork();
                
if  (nPid  >   0 )
                {
                    bIsChild 
=   false ;
                    
-- nWorker;
                }
                
else   if  ( 0   ==  nPid)
                {
                    bIsChild 
=   true ;
                    printf(
" create worker %d success!/n " , ::getpid());
                }
                
else
                {
                    printf(
" fork error: %s/n " , ::strerror(errno));
                    
return   - 1 ;
                }
            }
            
else  
            {
                
int  nStatus;
                
if  ( - 1   ==  ::wait( & nStatus))
                {
                    
++ nWorker;
                }
            }
        }
    }

    
return   0 ;
}

void  setnonblocking( int  sock)
{
    
int  opts;
    opts
= fcntl(sock,F_GETFL);
    
if (opts < 0 )
    {
        perror(
" fcntl(sock,GETFL) " );
        exit(
1 );
    }
    opts 
=  opts | O_NONBLOCK;
    
if (fcntl(sock,F_SETFL,opts) < 0 )
    {
        perror(
" fcntl(sock,SETFL,opts) " );
        exit(
1 );
    }   
}

int  main()
{
    
int  i, maxi, listenfd, connfd, sockfd,epfd,nfds;
    ssize_t n;
    
char  line[MAXLINE];
    socklen_t clilen;
    
struct  epoll_event ev,events[ 20 ];

    
struct  sockaddr_in clientaddr;
    
struct  sockaddr_in serveraddr;
    listenfd 
=  socket(AF_INET, SOCK_STREAM,  0 );
       bzero(
& serveraddr,  sizeof (serveraddr));
    serveraddr.sin_family 
=  AF_INET;
    
char   * local_addr = " 127.0.0.1 " ;
    inet_aton(local_addr,
& (serveraddr.sin_addr)); // htons(SERV_PORT);
    serveraddr.sin_port = htons(SERV_PORT);
      
//  地址重用
     int  nOptVal  =   1 ;
    socklen_t nOptLen 
=   sizeof ( int );
    
if  ( - 1   ==  ::setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,  & nOptVal, nOptLen))
    {
        
return   - 1 ;
    }    
    setnonblocking(listenfd);
    bind(listenfd,(sockaddr 
* ) & serveraddr,  sizeof (serveraddr));
    listen(listenfd, LISTENQ);    
    
    CreateWorker(
5 );
    
    
// 把socket设置为非阻塞方式
    
    
// 生成用于处理accept的epoll专用的文件描述符
    epfd = epoll_create( 256 );    
    
// 设置与要处理的事件相关的文件描述符
    ev.data.fd = listenfd;
    
// 设置要处理的事件类型
    ev.events = EPOLLIN | EPOLLET;
    
// ev.events=EPOLLIN;
    
// 注册epoll事件
    epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd, & ev);
 
     
// CreateWorker(5);
     
    maxi 
=   0 ;
    
    task_t task; 
    task_t 
* ptask;
    
while ( true
    {
        
// 等待epoll事件的发生
        nfds = epoll_wait(epfd,events, 20 , 500 );
        
// 处理所发生的所有事件     
         for (i = 0 ;i < nfds; ++ i)
        {
            
if (events[i].data.fd == listenfd)
            {                
                connfd 
=  accept(listenfd,NULL, NULL);
                
if (connfd < 0 ){                    
                    printf(
" connfd<0, listenfd = %d/n " , listenfd);
                    printf(
" error = %s/n " , strerror(errno));
                    exit(
1 );
                }
                setnonblocking(connfd);
               
                
// 设置用于读操作的文件描述符
                memset( & task,  0 sizeof (task));
                task.fd 
=  connfd;
                ev.data.ptr 
=   & task;
                
// 设置用于注册的读操作事件
                ev.events = EPOLLIN | EPOLLET;
                
// ev.events=EPOLLIN;
                
// 注册ev
                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd, & ev);
            }
            
else   if (events[i].events & EPOLLIN)
            {
                cout 
<<   " EPOLLIN "   <<  endl;
                ptask 
=  (task_t * )events[i].data.ptr;
                sockfd 
=  ptask -> fd;
                
                
if  ( (ptask -> =  read(sockfd, ptask -> buffer,  100 ))  <   0 ) {
                    
if  (errno  ==  ECONNRESET) {
                        close(sockfd);
                        events[i].data.ptr 
=  NULL;
                    } 
else
                        std::cout
<< " readline error " << std::endl;
                } 
else   if  (ptask -> ==   0 ) {
                    close(sockfd);
                    events[i].data.ptr 
=  NULL;
                }
                ptask
-> buffer[ptask -> n]  =   ' /0 ' ;
                cout 
<<   " read  "   <<  ptask -> buffer  <<  endl;
                
                
// 设置用于写操作的文件描述符                                
                ev.data.ptr  =  ptask;
                
// 设置用于注测的写操作事件
                ev.events = EPOLLOUT | EPOLLET;
                                
                
// 修改sockfd上要处理的事件为EPOLLOUT
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd, & ev);
            }
            
else   if (events[i].events & EPOLLOUT)
            {   
                cout 
<<   " EPOLLOUT "   <<  endl;
                ptask 
=  (task_t * )events[i].data.ptr;
                sockfd 
=  ptask -> fd;
                
                write(sockfd, ptask
-> buffer, ptask -> n);
                
                
// 设置用于读操作的文件描述符              
                ev.data.ptr  =  ptask;
                
                
// 修改sockfd上要处理的事件为EPOLIN
                epoll_ctl(epfd,EPOLL_CTL_DEL,sockfd, & ev);
                cout 
<<   " write  "   <<  ptask -> buffer;
                memset(ptask, 
0 sizeof ( * ptask));
                close(sockfd);
            }
        }
    }
    
return   0 ;
}


测试客户端:

# !/ usr / bin / perl

use strict;
use Socket;
use IO::Handle;

sub echoclient
{
    my $host 
=   " 127.0.0.1 " ;
    my $port 
=   5000 ;

    my $protocol 
=  getprotobyname( " TCP " );
    $host 
=  inet_aton($host);

    socket(SOCK, AF_INET, SOCK_STREAM, $protocol) or die 
" socket() failed: $! " ;

    my $dest_addr 
=  sockaddr_in($port, $host);
    connect(SOCK, $dest_addr) or die 
" connect() failed: $! " ;

    SOCK
-> autoflush( 1 );

    my $msg_out 
=   " hello world/n " ;
    print 
" out =  " , $msg_out;
    print SOCK $msg_out;
    my $msg_in 
=   < SOCK > ;
    print 
" in =  " , $msg_in;

    close SOCK;
}

#
& echoclient;
#exit(
0 );

for  (my $i  =   0 ; $i  <   9999 ; $i ++ )
{
    echoclient;
}



我查看了lighttpd的实现,也是在创建完子进程之后才创建的epoll的fd.

请问谁知道哪里有讲解这个的文档?



这是美丽的分割线:
-----------------------------------------------------------------------
感谢luke, 他帮我解释了这个问题的原因:

假如fd1是由A进程加入epfd的,而且用的是ET模式,那么加入通知的是进程B,显然B进程不会对fd1进行处理,所以以后fd1的事件再不会通知,所以经过几次循环之后,所有的fd都没有事件通知了,所以epoll_wait在timeout之后就返回0了。而在客户端的结果可想而知,只能是被阻塞。

也就是说, 这是一种发生在epoll fd上面的类似于"惊群"的现象.

你可能感兴趣的:(多进程服务器中,epoll的创建应该在创建子进程之后)