epoll 模型原理详解以及实例(3)

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;
  //声明epoll_event结构体的变量, ev用于注册事件, events数组用于回传要处理的事件
  struct epoll_event ev,events[20];
  //生成用于处理accept的epoll专用的文件描述符, 指定生成描述符的最大范围为256
  epfd = epoll_create(256);
  struct sockaddr_in clientaddr;
  struct sockaddr_in serveraddr;
  listenfd = socket(AF_INET, SOCK_STREAM, 0);

setnonblocking(listenfd);      //把用于监听的socket设置为非阻塞方式
  ev.data.fd = listenfd;          //设置与要处理的事件相关的文件描述符
  ev.events = EPOLLIN | EPOLLET;  //设置要处理的事件类型
  epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);    //注册epoll事件
  bzero(&serveraddr, sizeof(serveraddr));
  serveraddr.sin_family = AF_INET;
  char *local_addr = "200.200.200.204";
  inet_aton(local_addr, &(serveraddr.sin_addr));
  serveraddr.sin_port = htons(SERV_PORT);  //或者htons(SERV_PORT);
  bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
  listen(listenfd, LISTENQ);

maxi = 0;
  for( ; ; )
  {
      nfds = epoll_wait(epfd, events, 20, 500); //等待epoll事件的发生
      for(i = 0; i < nfds; ++i)                //处理所发生的所有事件
      {
        if(events[i].data.fd == listenfd)      //监听事件
        {
            connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen);
            if(connfd < 0)
            {
              perror("connfd<0");
              exit(1);
            }
            setnonblocking(connfd);          //把客户端的socket设置为非阻塞方式
            char *str = inet_ntoa(clientaddr.sin_addr);
            std::cout << "connect from " << str  <<std::endl;
            ev.data.fd=connfd;                //设置用于读操作的文件描述符
            ev.events=EPOLLIN | EPOLLET;      //设置用于注测的读操作事件
            epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            //注册ev事件
        }
        else if(events[i].events&EPOLLIN)      //读事件
        {
            if ( (sockfd = events[i].data.fd) < 0)
            {
              continue;
            }
            if ( (n = read(sockfd, line, MAXLINE)) < 0) // 这里和IOCP不同
            {
              if (errno == ECONNRESET)
              {
                  close(sockfd);
                  events[i].data.fd = -1;
              }
              else
              {
                  std::cout<<"readline error"<<std::endl;
              }
            }
            else if (n == 0)
            {
              close(sockfd);
              events[i].data.fd = -1;
            }
            ev.data.fd=sockfd;              //设置用于写操作的文件描述符
            ev.events=EPOLLOUT | EPOLLET;  //设置用于注测的写操作事件
            //修改sockfd上要处理的事件为EPOLLOUT
            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
        }
        else if(events[i].events&EPOLLOUT)//写事件
        {
            sockfd = events[i].data.fd;
            write(sockfd, line, n);
            ev.data.fd = sockfd;              //设置用于读操作的文件描述符
            ev.events = EPOLLIN | EPOLLET;    //设置用于注册的读操作事件
            //修改sockfd上要处理的事件为EPOLIN
            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
        }
      }
  }
}9.epoll进阶思考
9.1. 问题来源
最近学习EPOLL模型,介绍中说将EPOLL与Windows IOCP模型进行比较,说其的优势在于解决了IOCP模型大量线程上下文切换的开销,于是可以看出,EPOLL模型不需要多线程,即单线程中可以处理EPOLL逻辑。如果引入多线程反而会引起一些问题。但是EPOLL模型的服务器端到底可以不可以用多线程技术,如果可以,改怎么取舍,这成了困扰我的问题。上网查了一下,有这样几种声音:
(1) “要么事件驱动(如epoll),要么多线程,要么多进程,把这几个综合起来使用,感觉更加麻烦。”;
(2) “单线程使用epoll,但是不能发挥多核;多线程不用epoll。”;
(3) “主通信线程使用epoll所有需要监控的FD,有事件交给多线程去处理”;
(4) “既然用了epoll, 那么线程就不应该看到fd, 而只看到的是一个一个的业务请求/响应; epoll将网络数据组装成业务数据后, 转交给业务线程进行处理。这就是常说的半同步半异步”。
我比较赞同上述(3)、(4)中的观点
EPOLLOUT只有在缓冲区已经满了,不可以发送了,过了一会儿缓冲区中有空间了,就会触发EPOLLOUT,而且只触发一次。如果你编写的程序的网络IO不大,一次写入的数据不多的时候,通常都是epoll_wait立刻就会触发 EPOLLOUT;如果你不调用 epoll,直接写 socket,那么情况就取决于这个socket的缓冲区是不是足够了。如果缓冲区足够,那么写就成功。如果缓冲区不足,那么取决你的socket是不是阻塞的,要么阻塞到写完成,要么出错返回。所以EPOLLOUT事件具有较大的随机性,ET模式一般只用于EPOLLIN, 很少用于EPOLLOUT。
9.2. 具体做法
(1) 主通信线程使用epoll所有需要监控的FD,负责监控listenfd和connfd,这里只监听EPOLLIN事件,不监听EPOLLOUT事件;
(2) 一旦从Client收到了数据以后,将其构造成一个消息,放入消息队列中;
(3) 若干工作线程竞争,从消息队列中取出消息并进行处理,然后把处理结果发送给客户端。发送客户端的操作由工作线程完成。直接进行write。write到EAGAIN或EWOULDBLOCK后,线程循环continue等待缓冲区队列
发送函数代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/07056860132b8a0bd306cfbb0a535cbb.html