找回密码
 注册
搜索
热搜: 超星 读书 找书
查看: 1962|回复: 0

[【推荐】] 多进程服务器中,epoll的创建应该在创建子进程之后

[复制链接]
发表于 2009-8-6 14:27:08 | 显示全部楼层 |阅读模式
看我的测试代码,似乎应该是在创建子进程之后创建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.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.events&EPOLLIN)
      {
        cout << \"EPOLLIN\" << endl;
        ptask = (task_t*)events.data.ptr;
        sockfd = ptask->fd;
        
        if ( (ptask->n = read(sockfd, ptask->buffer, 100)) < 0) {
          if (errno == ECONNRESET) {
            close(sockfd);
            events.data.ptr = NULL;
          } else
            std::cout<<\"readline error\"<<std::endl;
        } else if (ptask->n == 0) {
          close(sockfd);
          events.data.ptr = NULL;
        }
        ptask->buffer[ptask->n] = &#39;\\0&#39;;
        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.events&EPOLLOUT)
      {  
        cout << \"EPOLLOUT\" << endl;
        ptask = (task_t*)events.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上面的类似于\"惊群\"的现象.
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

Archiver|手机版|小黑屋|网上读书园地

GMT+8, 2024-11-3 02:24 , Processed in 0.115801 second(s), 5 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表