IO多路转接——epoll进阶

简介: IO多路转接——epoll进阶

事件模型


EPOLL事件有两种模型:


  • Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。


  • Level Triggered (LT) 水平触发只要有数据都会触发。


ET模式


ET模式即Edge Triggered工作模式。


如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。


只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。


epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。


  1. 基于非阻塞文件句柄


  1. 只有当read或者write返回EAGAIN(非阻塞读,暂时无数据)时才需要挂起、等待。但这并不是说每次read时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。


LT模式


LT模式即Level Triggered工作模式。


与ET模式不同的是,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,无论后面的数据是否被使用。


LT(level triggered):LT是缺省的工作方式,并且同时支持block和no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。


ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once).


实例一:基于管道epoll ET触发模式


#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#define MAXLINE 10
int main(int argc, char *argv[])
{
  int efd, i;
  int pfd[2];
  pid_t pid;
  char buf[MAXLINE], ch = 'a';
  pipe(pfd);
  pid = fork();
  if (pid == 0) {
    close(pfd[0]);
    while (1) {
      for (i = 0; i < MAXLINE/2; i++)
        buf[i] = ch;
      buf[i-1] = '\n';
      ch++;
      for (; i < MAXLINE; i++)
        buf[i] = ch;
      buf[i-1] = '\n';
      ch++;
      write(pfd[1], buf, sizeof(buf));
      sleep(2);
    }
    close(pfd[1]);
  } else if (pid > 0) {
    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;
    close(pfd[1]);
    efd = epoll_create(10);
    /* event.events = EPOLLIN; */
    event.events = EPOLLIN | EPOLLET;   /* ET 边沿触发 ,默认是水平触发 */
    event.data.fd = pfd[0];
  epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
    while (1) {
      res = epoll_wait(efd, resevent, 10, -1);
      printf("res %d\n", res);
      if (resevent[0].data.fd == pfd[0]) {
        len = read(pfd[0], buf, MAXLINE/2);
        write(STDOUT_FILENO, buf, len);
      }
    }
    close(pfd[0]);
    close(efd);
  } else {
    perror("fork");
    exit(-1);
  }
  return 0;
}


实例二:基于网络C/S模型的epoll ET触发模式


server


/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(void)
{
  struct sockaddr_in servaddr, cliaddr;
  socklen_t cliaddr_len;
  int listenfd, connfd;
  char buf[MAXLINE];
  char str[INET_ADDRSTRLEN];
  int i, efd;
  listenfd = socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  listen(listenfd, 20);
  struct epoll_event event;
  struct epoll_event resevent[10];
  int res, len;
  efd = epoll_create(10);
  event.events = EPOLLIN | EPOLLET;   /* ET 边沿触发 ,默认是水平触发 */
  printf("Accepting connections ...\n");
  cliaddr_len = sizeof(cliaddr);
  connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
  printf("received from %s at PORT %d\n",
      inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
      ntohs(cliaddr.sin_port));
  event.data.fd = connfd;
  epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
  while (1) {
    res = epoll_wait(efd, resevent, 10, -1);
    printf("res %d\n", res);
    if (resevent[0].data.fd == connfd) {
      len = read(connfd, buf, MAXLINE/2);
      write(STDOUT_FILENO, buf, len);
    }
  }
  return 0;
}


client


/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(int argc, char *argv[])
{
  struct sockaddr_in servaddr;
  char buf[MAXLINE];
  int sockfd, i;
  char ch = 'a';
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
  servaddr.sin_port = htons(SERV_PORT);
  connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  while (1) {
    for (i = 0; i < MAXLINE/2; i++)
      buf[i] = ch;
    buf[i-1] = '\n';
    ch++;
    for (; i < MAXLINE; i++)
      buf[i] = ch;
    buf[i-1] = '\n';
    ch++;
    write(sockfd, buf, sizeof(buf));
    sleep(10);
  }
  Close(sockfd);
  return 0;
}


实例三:基于网络C/S非阻塞模型的epoll ET触发模式


server


/* server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(void)
{
  struct sockaddr_in servaddr, cliaddr;
  socklen_t cliaddr_len;
  int listenfd, connfd;
  char buf[MAXLINE];
  char str[INET_ADDRSTRLEN];
  int i, efd, flag;
  listenfd = socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(SERV_PORT);
  bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  listen(listenfd, 20);
  struct epoll_event event;
  struct epoll_event resevent[10];
  int res, len;
  efd = epoll_create(10);
  /* event.events = EPOLLIN; */
  event.events = EPOLLIN | EPOLLET;   /* ET 边沿触发 ,默认是水平触发 */
  printf("Accepting connections ...\n");
  cliaddr_len = sizeof(cliaddr);
  connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
  printf("received from %s at PORT %d\n",
      inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
      ntohs(cliaddr.sin_port));
  flag = fcntl(connfd, F_GETFL);
  flag |= O_NONBLOCK;
  fcntl(connfd, F_SETFL, flag);
  event.data.fd = connfd;
  epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
  while (1) {
    printf("epoll_wait begin\n");
    res = epoll_wait(efd, resevent, 10, -1);
    printf("epoll_wait end res %d\n", res);
    if (resevent[0].data.fd == connfd) {
      while ((len = read(connfd, buf, MAXLINE/2)) > 0)
        write(STDOUT_FILENO, buf, len);
    }
  }
  return 0;
}


client


/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 8080
int main(int argc, char *argv[])
{
  struct sockaddr_in servaddr;
  char buf[MAXLINE];
  int sockfd, i;
  char ch = 'a';
  sockfd = socket(AF_INET, SOCK_STREAM, 0);
  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
  servaddr.sin_port = htons(SERV_PORT);
  connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
  while (1) {
    for (i = 0; i < MAXLINE/2; i++)
      buf[i] = ch;
    buf[i-1] = '\n';
    ch++;
    for (; i < MAXLINE; i++)
      buf[i] = ch;
    buf[i-1] = '\n';
    ch++;
    write(sockfd, buf, sizeof(buf));
    sleep(10);
  }
  Close(sockfd);
  return 0;
}


目录
打赏
0
相关文章
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
手撕测试tcp服务器效率工具——以epoll和io_uring对比为例
171 2
|
8月前
|
io复用之epoll核心源码剖析
epoll底层实现中有两个关键的数据结构,一个是eventpoll另一个是epitem,其中eventpoll中有两个成员变量分别是rbr和rdlist,前者指向一颗红黑树的根,后者指向双向链表的头。而epitem则是红黑树节点和双向链表节点的综合体,也就是说epitem即可作为树的节点,又可以作为链表的节点,并且epitem中包含着用户注册的事件。当用户调用epoll_create()时,会创建eventpoll对象(包含一个红黑树和一个双链表);
139 0
io复用之epoll核心源码剖析
TCP服务器 IO多路复用的实现:select、poll、epoll
TCP服务器 IO多路复用的实现:select、poll、epoll
109 0
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
Linux C/C++ 开发(学习笔记十二 ):TCP服务器(并发网络编程io多路复用epoll)
135 0
|
8月前
|
图解IO多路复用模型之select、poll、epoll
图解IO多路复用模型之select、poll、epoll
148 0
与epoll媲美的io_uring
与epoll媲美的io_uring
78 0
|
3月前
|
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
78 0
Linux C/C++之IO多路复用(poll,epoll)
(七)Java网络编程-IO模型篇之从BIO、NIO、AIO到内核select、epoll剖析!
IO(Input/Output)方面的基本知识,相信大家都不陌生,毕竟这也是在学习编程基础时就已经接触过的内容,但最初的IO教学大多数是停留在最基本的BIO,而并未对于NIO、AIO、多路复用等的高级内容进行详细讲述,但这些却是大部分高性能技术的底层核心,因此本文则准备围绕着IO知识进行展开。
210 1
(八)Java网络编程之IO模型篇-内核Select、Poll、Epoll多路复用函数源码深度历险!
select/poll、epoll这些词汇相信诸位都不陌生,因为在Redis/Nginx/Netty等一些高性能技术栈的底层原理中,大家应该都见过它们的身影,接下来重点讲解这块内容。
113 0
Java IO流进阶教程:掌握字节流和字符流的高级用法!
【6月更文挑战第26天】Java IO流助你高效交换数据,包括字节流(InputStream/OutputStream)和字符流(Reader/Writer)的高级技巧。缓冲流(Buffered*)提升读写性能,对象流(Object*Stream)支持对象序列化。字符流的BufferedReader/BufferedWriter优化文本处理,注意字符集如UTF-8用于编码转换。掌握这些,优化IO操作,提升代码质量。
63 0

热门文章

最新文章