epoll 总结

epoll 总结

epoll概述

    在网络通信中,由于网络环境复杂、程序的健壮性不佳等因素的存在,会造成通信双方不能正常通信。

        常见的不稳定因素如下:

        1)服务器进程终止。客户端在不知道服务器进程已终止的情况下发送数据给服务器。如果客户端程序

           没有处理SIGPIPE信号,客户端程序会崩溃。

        2)客户端与服务器的网络不通。客户端发送的数据可能会超时返回,如果客户端提前知道连接不通则

           可以立即返回。

        IO复用的作用就是告知系统内核一旦发现进程指定的一个或多个IO条件就绪就通知进程,进程再进行处

        理。

        因此,在网络通信中使用IO复用技术很重要。        

        epoll属于IO复用的一种,在高并发中常用epoll+non-blocking+aio(异步)+线程池的组合的方式

        来使用,从而产生强大的并发威力。

        在下一篇中我会介绍 Nginx高并发实现的相关技术。本文将说明epoll+nonblocking 的使用方式。


epoll接口说明

 #include <sys/epoll.h>

 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

该系统调用对文件描述符epfd引用的epoll实例执行控制操作。 它要求对目标文件描述符fd执行op操作。

参数说明

op

EPOLL_CTL_ADD

        在文件描述符epfd引用的epoll实例上注册目标文件描述符fd,并将事件event与链接到fd的内部文件相关联。

EPOLL_CTL_MOD

        更改与目标文件描述符fd关联的事件event。

EPOLL_CTL_DEL

        从epfd引用的epoll实例中删除(注销)目标文件描述符fd。 该事件被忽略并可以为NULL。


event 相关类型的定义

event参数描述链接到文件描述符fd的对象。 结构epoll_event被定义为:


        typedef union epoll_data {

               void        *ptr;

               int          fd;

               uint32_t     u32;

               uint64_t     u64;

        } epoll_data_t;


        struct epoll_event {

               uint32_t     events;      /* Epoll events */

               epoll_data_t data;        /* User data variable */

        };

                

        struct epoll_event结构体中的events参数如下:

        EPOLLIN

                关联的文件可用于read操作。

        EPOLLOUT

                关联的文件可用于write操作。

        EPOLLRDHUP(自Linux 2.6.17开始)

                流套接字对等关闭连接,或关闭写入连接的一半。 (当使用边缘触发监控时,该标志对编写

                简单的代码来检测对等关闭特别有用。)

        EPOLLPRI

                紧急数据可用于读取操作。

       EPOLLERR

                相关文件描述符发生错误情况。 epoll_wait会一直等待这个事件; 没有必要将其设置为

                事件。

        EPOLLHUP

                挂起事件发生在关联的文件描述符上。 epoll_wait会一直等待这个事件; 没有必要将其

                设置为事件。

        EPOLLET

                设置关联文件描述符的边缘触发行为。 epoll的默认行为是Level Triggered。 有关

                Edge和Level Triggered事件分发体系结构的更多详细信息。


        EPOLLONESHOT(自Linux 2.6.2开始)

        设置关联文件描述符的一次性行为。 这意味着在事件被epoll_wait拉出之后,关联的文件描述符会

        在内部被禁用,并且epoll接口不会报告其他事件。

        用户必须使用EPOLL_CTL_MOD调用epoll_ctl()以使用新的事件掩码重新装入文件描述符。

epoll_ctl常见返回结果及错误

        成功时,epoll_ctl()返回零。 当发生错误时,epoll_ctl()返回-1,并且适当地设置errno。

epoll_ctl常见返回错误

EBADF

        epfd或fd不是有效的文件描述符。

EEXIST

        是EPOLL_CTL_ADD,并且提供的文件描述符fd已经在此epoll实例中注册。

EINVAL

        epfd不是epoll文件描述符,或者fd与epfd相同,或者所请求的操作操作不受此接口支持。

ENOENT 

        op是EPOLL_CTL_MOD或EPOLL_CTL_DEL,并且fd未在此epoll实例中注册。

ENOMEM

        没有足够的内存来处理请求的操作控制操作。

ENOSPC

        在尝试在epoll实例上注册(EPOLL_CTL_ADD)新文件描述符时遇到

      /proc/sys/fs/epoll/max_user_watches所施加的限制。 更多细节见epoll(7)。


EPERM   目标文件fd不支持epoll。


epoll 测试代码

服务器端代码

代码来自以下路径:

https://github.com/eklitzke/epollet 

#include <errno.h>

#include <fcntl.h>

#include <netinet/in.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/epoll.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <unistd.h>



#define MAXEVENTS 64

#define PORT 9000



static void set_nonblocking(int fd) {

  int flags = fcntl(fd, F_GETFL, 0);

  if (flags == -1) {

    perror("fcntl()");

    return;

  }

  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {

    perror("fcntl()");

  }

}



int main(int argc, char **argv) {

  // create the server socket

  int sock = socket(AF_INET, SOCK_STREAM, 0);

  if (sock == -1) {

    perror("socket()");

    return 1;

  }

  int enable = 1;

  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) ==

      -1) {

    perror("setsockopt()");

    return 1;

  }



  // bind

  struct sockaddr_in addr;

  memset(&addr, 0, sizeof(addr));

  addr.sin_family = AF_INET;

  addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  addr.sin_port = htons(PORT);

  if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {

    perror("bind()");

    return 1;

  }



  // 设置socket nonblocking, listen

  set_nonblocking(sock);

  if (listen(sock, SOMAXCONN) < 0) {

    perror("listen()");

    return 1;

  }



  // 创建 epoll socket

  int epoll_fd = epoll_create1(0);

  if (epoll_fd == -1) {

    perror("epoll_create1()");

    return 1;

  }



  // 设置 server socket for reading, 设置edge-triggered

  //要使用边缘触发轮询,您必须将文件描述符置于非阻塞模式。 然后你必须调用读或写,直到他们每次都返回EWOULDBLOCK。

  struct epoll_event event;

  memset(&event, 0, sizeof(event));

  event.data.fd = sock;

  event.events = EPOLLIN | EPOLLET;

  if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event) == -1) {

    perror("epoll_ctl()");

    return 1;

  }



  struct epoll_event *events = calloc(MAXEVENTS, sizeof(event));

  for (;;) {

        //epoll阻塞等待客户端发起连接

    int nevents = epoll_wait(epoll_fd, events, MAXEVENTS, -1);

    if (nevents == -1) {

      perror("epoll_wait()");

      return 1;

    }

    for (int i = 0; i < nevents; i++) {//循环读取事件

      if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) ||

          (!(events[i].events & EPOLLIN))) {

        // error case

        fprintf(stderr, "epoll error\n");

        close(events[i].data.fd);

        continue;

      } else if (events[i].data.fd == sock) {

        // server socket; call accept as many times as we can

        for (;;) {

          struct sockaddr in_addr;

          socklen_t in_addr_len = sizeof(in_addr);

                        //与客户端建立连接

          int client = accept(sock, &in_addr, &in_addr_len);

          if (client == -1) {

            if (errno == EAGAIN || errno == EWOULDBLOCK) {

              // we processed all of the connections

              break;

            } else {

              perror("accept()");

              return 1;

            }

          } else {

            printf("accepted new connection on fd %d\n", client);

            set_nonblocking(client);

            event.data.fd = client;

event.events = EPOLLIN | EPOLLET;

//客户端和服务器端建立链接后,服务器端把建立链接的描述符加

//入事件

            if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client, &event) == -1) {

              perror("epoll_ctl()");

              return 1;

            }

          }

        }

      } else {

        // client socket; read as much data as we can

        char buf[1024];

        for (;;) {

                        //读取已建立链接的用户的数据

          ssize_t nbytes = read(events[i].data.fd, buf, sizeof(buf));

          if (nbytes == -1) {

            if (errno == EAGAIN || errno == EWOULDBLOCK) {

              printf("finished reading data from client\n");

              break;

            } else {

              perror("read()");

              return 1;

            }

          } else if (nbytes == 0) {

                        //客户端断开链接

            printf("finished with %d\n", events[i].data.fd);

            close(events[i].data.fd);

            break;

          } else {

            fwrite(buf, sizeof(char), nbytes, stdout);

          }

        }

      }

    }

  }

  return 0;

}



客户端代码


#include <errno.h>

#include <fcntl.h>

#include <netinet/in.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <sys/epoll.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <unistd.h>



void str_cli(FILE *fp, int sockfd)

{

        char    sendline[MAXLINE], recvline[MAXLINE];



        while (fgets(sendline, MAXLINE, fp) != NULL) {



                writen(sockfd, sendline, strlen(sendline));

        }

}



int main(int argc, char **argv)

{

        int                                     sockfd;

        struct sockaddr_in      servaddr;



        if (argc != 2)

                err_quit("usage: tcpcli <IPaddress>");



        sockfd = socket(AF_INET, SOCK_STREAM, 0);



        bzero(&servaddr, sizeof(servaddr));

        servaddr.sin_family = AF_INET;

        servaddr.sin_port = htons(9000);

        Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);



        connect(sockfd, (SA *) &servaddr, sizeof(servaddr));



        str_cli(stdin, sockfd);         /* do it all */



        exit(0);



}


参考资料

[1]. http://man7.org/linux/man-pages/man7/epoll.7.html 

[2]. https://eklitzke.org/blocking-io-nonblocking-io-and-epoll 


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值