c/c++语言开发共享Linux 下epoll 网络模型

为什么需要epoll? 基于select 的I/O 复用技术速度过慢,从代码上分析,最主要的两点是 1. 每次调用select 函数是都需要向改函数传递对象信息 2. 需要遍历所有文件描述符才能获取有变化的文件描述符 epoll 不需要以上两点操作 epoll 函数介绍 epoll_create e …


为什么需要epoll?

基于select 的i/o 复用技术速度过慢,从代码上分析,最主要的两点是

  1. 每次调用select 函数是都需要向改函数传递对象信息
  2. 需要遍历所有文件描述符才能获取有变化的文件描述符

epoll 不需要以上两点操作

epoll 函数介绍

epoll_create

/* creates an epoll instance.  returns an fd for the new instance.    the "size" parameter is a hint specifying the number of file    descriptors to be associated with the new instance.  the fd    returned by epoll_create() should be closed with close().  */ extern int epoll_create (int __size) __throw; 

epoll_ctl

/* manipulate an epoll instance "epfd". returns 0 in case of success,    -1 in case of error ( the "errno" variable will contain the    specific error code ) the "op" parameter is one of the epoll_ctl_*    constants defined above. the "fd" parameter is the target of the    operation. the "event" parameter describes which events the caller    is interested in and any associated user data.  */ extern int epoll_ctl (int __epfd, int __op, int __fd, 		      struct epoll_event *__event) __throw; 

epoll_wait

/* wait for events on an epoll instance "epfd". returns the number of    triggered events returned in "events" buffer. or -1 in case of    error with the "errno" variable set to the specific error code. the    "events" parameter is a buffer that will contain triggered    events. the "maxevents" is the maximum number of events to be    returned ( usually size of "events" ). the "timeout" parameter    specifies the maximum wait time in milliseconds (-1 == infinite).     this function is a cancellation point and therefore not marked with    __throw.  */ extern int epoll_wait (int __epfd, struct epoll_event *__events, 		       int __maxevents, int __timeout);  

epoll_event

struct epoll_event {   uint32_t events;	/* epoll events */   epoll_data_t data;	/* user data variable */ } __epoll_packed; 

epoll_data_t

typedef union epoll_data {   void *ptr;   int fd;   uint32_t u32;   uint64_t u64; } epoll_data_t; 

利用epoll i/o 复用的服务端

服务端

  1. 建立套接字
  2. 绑定端口
  3. 监听客户端请求状态
  4. 设置epollfd
  5. 调用epoll_wait
    • 如果是服务端套接字发生变化,说明有新的连接,调用accept 函数受理
    • 如果不是服务端套接字发生变化,则响应客户端
  6. 关闭

代码如下:

epoll.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h>  #define buf_size 100 #define epoll_size 50  void error_handling(char *message) {     fputs(message, stderr);     fputc('n', stderr);     exit(1); } int main(int argc, char *argv[]) {     int serv_sock, clnt_sock;     struct sockaddr_in serv_adr, clnt_adr;     socklen_t adr_sz;     int str_len, i;     char buf[buf_size];      struct epoll_event *ep_events;     struct epoll_event event;     int epfd, event_cnt;      if (argc != 2)     {         printf("usage : %s <port> n", argv[0]);         exit(1);     }     serv_sock = socket(pf_inet, sock_stream, 0);     memset(&serv_adr, 0, sizeof(serv_adr));     serv_adr.sin_family = af_inet;     serv_adr.sin_addr.s_addr = htonl(inaddr_any);     serv_adr.sin_port = htons(atoi(argv[1]));      if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)         error_handling("bind error");     if (listen(serv_sock, 5) == -1)         error_handling("listen error");      epfd = epoll_create(epoll_size); //可忽略这个参数,填入的参数为操作系统参考     ep_events = malloc(sizeof(struct epoll_event) * epoll_size);      event.events = epollin; //读取数据     event.data.fd = serv_sock;     epoll_ctl(epfd, epoll_ctl_add, serv_sock, &event); //epoll例程epfd 中添加文件描述符 serv_sock,目的是监听 enevt 中的事件      while (1)     {         event_cnt = epoll_wait(epfd, ep_events, epoll_size, -1); //获取改变了的文件描述符,返回数量         if (event_cnt == -1)         {             puts("epoll_wait error");             break;         }          for (i = 0; i < event_cnt; i++)         {             if (ep_events[i].data.fd == serv_sock) //客户端请求连接             {                 adr_sz = sizeof(clnt_adr);                 clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);                 event.events = epollin;                 event.data.fd = clnt_sock; //把客户端套接字添加进去                 epoll_ctl(epfd, epoll_ctl_add, clnt_sock, &event);                 printf("connected client : %d n", clnt_sock);             }             else //客户端套接字             {                 str_len = read(ep_events[i].data.fd, buf, buf_size);                 if (str_len == 0)                 {                     epoll_ctl(epfd, epoll_ctl_del, ep_events[i].data.fd, null); //从epoll中删除套接字                     close(ep_events[i].data.fd);                     printf("closed client : %d n", ep_events[i].data.fd);                 }                 else                 {                     printf("message from client: %sn", buf);                      write(ep_events[i].data.fd, buf, str_len);                 }             }         }     }     close(serv_sock);     close(epfd);      return 0; }   

client.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>  #define buf_size 100 void error_handling(char *message) {     fputs(message, stderr);     fputc('n', stderr);     exit(1); }  int main(int argc, char *argv[]) {     int sock;     char message[buf_size]="hello world";     int str_len;     struct sockaddr_in serv_adr;      if (argc != 3)     {         printf("usage : %s <ip> <port>n", argv[0]);         exit(1);     }      sock = socket(pf_inet, sock_stream, 0);     if (sock == -1)         error_handling("socket error");      memset(&serv_adr, 0, sizeof(serv_adr));     serv_adr.sin_family = af_inet;     serv_adr.sin_addr.s_addr = inet_addr(argv[1]);     serv_adr.sin_port = htons(atoi(argv[2]));      if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)         error_handling("connect error!");     else         puts("connected...........");         write(sock, message, strlen(message));     str_len = read(sock, message, buf_size - 1);     message[str_len] = 0;     printf("message from server: %sn", message);      close(sock);     return 0; }  

运行结果

connected client : 5  message from client: hello world closed client : 5   connected........... message from server: hello world  

epoll 的两种触发模式

条件触发和边缘触发的区别在于发生时间的时间点

1.条件触发 lt

epoll 默认以条件触发方式工作

条件触发时只要输入缓冲区任有数据需要读取,就会注册新的事件

测试代码:

ltserver.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h>  #define buf_size 3 #define epoll_size 50 void error_handling(char *message) {     fputs(message, stderr);     fputc('n', stderr);     exit(1); }   int main(int argc, char *argv[]) {     int serv_sock, clnt_sock;     struct sockaddr_in serv_adr, clnt_adr;     socklen_t adr_sz;     int str_len, i;     char buf[buf_size];      struct epoll_event *ep_events;     struct epoll_event event;     int epfd, event_cnt;      if (argc != 2)     {         printf("usage : %s <port> n", argv[0]);         exit(1);     }     serv_sock = socket(pf_inet, sock_stream, 0);     memset(&serv_adr, 0, sizeof(serv_adr));     serv_adr.sin_family = af_inet;     serv_adr.sin_addr.s_addr = htonl(inaddr_any);     serv_adr.sin_port = htons(atoi(argv[1]));      if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)         error_handling("bind error");     if (listen(serv_sock, 5) == -1)         error_handling("listen error");      epfd = epoll_create(epoll_size);      ep_events = malloc(sizeof(struct epoll_event) * epoll_size);      event.events = epollin;      event.data.fd = serv_sock;     epoll_ctl(epfd, epoll_ctl_add, serv_sock, &event);       while (1)     {         event_cnt = epoll_wait(epfd, ep_events, epoll_size, -1); //获取改变了的文件描述符,返回数量          if (event_cnt == -1)         {             puts("epoll_wait error");             break;         }          puts("call epoll_wait");         for (i = 0; i < event_cnt; i++)         {              if (ep_events[i].data.fd == serv_sock)              {                 adr_sz = sizeof(clnt_adr);                 clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);                 event.events = epollin;                 event.data.fd = clnt_sock;                  epoll_ctl(epfd, epoll_ctl_add, clnt_sock, &event);                 printf("connected client : %d n", clnt_sock);             }             else              {                 str_len = read(ep_events[i].data.fd, buf, buf_size);                 if (str_len == 0)                 {                     epoll_ctl(epfd, epoll_ctl_del, ep_events[i].data.fd, null); //从epoll中删除套接字                     close(ep_events[i].data.fd);                     printf("closed client : %d n", ep_events[i].data.fd);                 }                 else                 {                     printf("message from client: %sn", buf);                      write(ep_events[i].data.fd, buf, str_len);                 }             }         }     }     close(serv_sock);     close(epfd);      return 0; }   

client.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>  #define buf_size 1024 void error_handling(char *message) {     fputs(message, stderr);     fputc('n', stderr);     exit(1); }  int main(int argc, char *argv[]) {     //sock     int sock;     char message[buf_size];     int str_len;     struct sockaddr_in serv_adr;      if (argc != 3)     {         printf("usage : %s <ip> <port>n", argv[0]);         exit(1);     }      sock = socket(pf_inet, sock_stream, 0);     if (sock == -1)         error_handling("socket error");      memset(&serv_adr, 0, sizeof(serv_adr));     serv_adr.sin_family = af_inet;     serv_adr.sin_addr.s_addr = inet_addr(argv[1]);     serv_adr.sin_port = htons(atoi(argv[2]));      if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)         error_handling("connect error!");     else         puts("connected...........");      while (1)     {         fputs("input message: ", stdout);         fgets(message, buf_size, stdin);         write(sock, message, strlen(message));         str_len = read(sock, message, buf_size - 1);         message[str_len] = 0;         printf("message from server: %sn", message);     }     close(sock);     return 0; }   

测试结果

call epoll_wait connected client : 5  call epoll_wait message from client: abc call epoll_wait message from client: def call epoll_wait message from client: g   connected........... input message: abcdefg message from server: abcdefg  

2.边缘触发

在边缘触发模式中输入缓冲收到数据时仅注册一次该事件。
也就是说会一次性读完所有数据或者一次性写完全部数据,那么在使用边缘触发时许将套接字改为非阻塞模式,否则可能因为较长的i/o 时间引起服务的卡顿。

测试代码:

etserver.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h>  #define buf_size 3  #define epoll_size 50 void error_handling(char *message) {     fputs(message, stderr);     fputc('n', stderr);     exit(1); } void setnonblockingmode(int fd) {     int flag = fcntl(fd, f_getfl, 0);     fcntl(fd, f_setfl, flag | o_nonblock); }  int main(int argc, char *argv[]) {     int serv_sock, clnt_sock;     struct sockaddr_in serv_adr, clnt_adr;     socklen_t adr_sz;     int str_len, i;     char buf[buf_size];      struct epoll_event *ep_events;     struct epoll_event event;     int epfd, event_cnt;      if (argc != 2)     {         printf("usage : %s <port> n", argv[0]);         exit(1);     }     serv_sock = socket(pf_inet, sock_stream, 0);     memset(&serv_adr, 0, sizeof(serv_adr));     serv_adr.sin_family = af_inet;     serv_adr.sin_addr.s_addr = htonl(inaddr_any);     serv_adr.sin_port = htons(atoi(argv[1]));      if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)         error_handling("bind error");     if (listen(serv_sock, 5) == -1)         error_handling("listen error");      epfd = epoll_create(epoll_size);      ep_events = malloc(sizeof(struct epoll_event) * epoll_size);      setnonblockingmode(serv_sock);     event.events = epollin;      event.data.fd = serv_sock;     epoll_ctl(epfd, epoll_ctl_add, serv_sock, &event);      while (1)     {         event_cnt = epoll_wait(epfd, ep_events, epoll_size, -1);          if (event_cnt == -1)         {             puts("epoll_wait error");             break;         }          puts("call epoll_wait");         for (i = 0; i < event_cnt; i++)         {             if (ep_events[i].data.fd == serv_sock)              {                 adr_sz = sizeof(clnt_adr);                 clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);                 setnonblockingmode(clnt_sock);                     event.events = epollin | epollet;                  event.data.fd = clnt_sock;                        epoll_ctl(epfd, epoll_ctl_add, clnt_sock, &event);                 printf("connected client : %d n", clnt_sock);             }             else              {                 while (1)                 {                     str_len = read(ep_events[i].data.fd, buf, buf_size);                     if (str_len == 0)                     {                         epoll_ctl(epfd, epoll_ctl_del, ep_events[i].data.fd, null); //从epoll中删除套接字                         close(ep_events[i].data.fd);                         printf("closed client : %d n", ep_events[i].data.fd);                         break;                     }                     else if (str_len < 0)                     {                         if (errno == eagain) //read 返回-1 且 errno 值为 eagain ,意味读取了输入缓冲的全部数据                             break;                     }                     else                     {                         printf("message from client: %sn", buf);                          write(ep_events[i].data.fd, buf, str_len);                     }                 }             }         }     }     close(serv_sock);     close(epfd);      return 0; }   

client.c

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h>  #define buf_size 1024 void error_handling(char *message) {     fputs(message, stderr);     fputc('n', stderr);     exit(1); }  int main(int argc, char *argv[]) {     //sock     int sock;     char message[buf_size];     int str_len;     struct sockaddr_in serv_adr;      if (argc != 3)     {         printf("usage : %s <ip> <port>n", argv[0]);         exit(1);     }      sock = socket(pf_inet, sock_stream, 0);     if (sock == -1)         error_handling("socket error");      memset(&serv_adr, 0, sizeof(serv_adr));     serv_adr.sin_family = af_inet;     serv_adr.sin_addr.s_addr = inet_addr(argv[1]);     serv_adr.sin_port = htons(atoi(argv[2]));      if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)         error_handling("connect error!");     else         puts("connected...........");      while (1)     {         fputs("input message: ", stdout);         fgets(message, buf_size, stdin);         write(sock, message, strlen(message));         str_len = read(sock, message, buf_size - 1);         message[str_len] = 0;         printf("message from server: %sn", message);     }     close(sock);     return 0; }   

测试结果

call epoll_wait connected client : 5  call epoll_wait message from client: abc message from client: def message from client: fg call epoll_wait closed client : 5   connected........... input message: abcdeffg message from server: abcdeffg input message: ^c   

边缘触发

分离了接受数据和处理数据的时间点 (一次性接受完数据,当有多个类别数据传输时可以保持各个类别数据的独立性,完整性由tcp保证)

本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。

ctvol管理联系方式QQ:251552304

本文章地址:https://www.ctvol.com/c-cdevelopment/599817.html

(0)
上一篇 2021年5月9日
下一篇 2021年5月9日

精彩推荐