一。epoll介绍
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
二。epoll函数
1. 创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。
2.控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
3.等待所监控文件描述符上有事件的产生,类似于select()调用。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <stdlib.h> 4 #include <string.h> 5 #include <arpa/inet.h> 6 #include <sys/epoll.h> 7 #include <errno.h> 8 #include <ctype.h> 9 10 #include "wrap.h" 11 12 #define MAXLINE 8192 13 #define SERV_PORT 8000 14 #define OPEN_MAX 5000 15 16 int main(int argc, char *argv[]) 17 { 18 int i, listenfd, connfd, sockfd; 19 int n, num = 0; 20 ssize_t nready, efd, res; 21 char buf[MAXLINE], str[INET_ADDRSTRLEN]; 22 socklen_t clilen; 23 24 struct sockaddr_in cliaddr, servaddr; 25 struct epoll_event tep, ep[OPEN_MAX]; //tep: epoll_ctl参数 ep[] : epoll_wait参数 26 27 listenfd = Socket(AF_INET, SOCK_STREAM, 0); 28 29 int opt = 1; 30 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); //端口复用 31 32 bzero(&servaddr, sizeof(servaddr)); 33 servaddr.sin_family = AF_INET; 34 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 35 servaddr.sin_port = htons(SERV_PORT); 36 37 Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); 38 39 Listen(listenfd, 20); 40 41 efd = epoll_create(OPEN_MAX); //创建epoll模型, efd指向红黑树根节点 42 if (efd == -1) 43 perr_exit("epoll_create error"); 44 45 tep.events = EPOLLIN; tep.data.fd = listenfd; //指定lfd的监听时间为"读" 46 res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep); //将lfd及对应的结构体设置到树上,efd可找到该树 47 if (res == -1) 48 perr_exit("epoll_ctl error"); 49 50 for ( ; ; ) { 51 /*epoll为server阻塞监听事件, ep为struct epoll_event类型数组, OPEN_MAX为数组容量, -1表永久阻塞*/ 52 nready = epoll_wait(efd, ep, OPEN_MAX, -1); 53 if (nready == -1) 54 perr_exit("epoll_wait error"); 55 56 for (i = 0; i < nready; i++) { 57 if (!(ep[i].events & EPOLLIN)) //如果不是"读"事件, 继续循环 58 continue; 59 60 if (ep[i].data.fd == listenfd) { //判断满足事件的fd是不是lfd 61 clilen = sizeof(cliaddr); 62 connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); //接受链接 63 64 printf("received from %s at PORT %d\n", 65 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), 66 ntohs(cliaddr.sin_port)); 67 printf("cfd %d---client %d\n", connfd, ++num); 68 69 tep.events = EPOLLIN; tep.data.fd = connfd; 70 res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep); 71 if (res == -1) 72 perr_exit("epoll_ctl error"); 73 74 } else { //不是lfd, 75 sockfd = ep[i].data.fd; 76 n = Read(sockfd, buf, MAXLINE); 77 78 if (n == 0) { //读到0,说明客户端关闭链接 79 res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); //将该文件描述符从红黑树摘除 80 if (res == -1) 81 perr_exit("epoll_ctl error"); 82 Close(sockfd); //关闭与该客户端的链接 83 printf("client[%d] closed connection\n", sockfd); 84 85 } else if (n < 0) { //出错 86 perror("read n < 0 error: "); 87 res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL); 88 Close(sockfd); 89 90 } else { //实际读到了字节数 91 for (i = 0; i < n; i++) 92 buf[i] = toupper(buf[i]); //转大写,写回给客户端 93 94 Write(STDOUT_FILENO, buf, n); 95 Writen(sockfd, buf, n); 96 } 97 } 98 } 99 } 100 Close(listenfd); 101 Close(efd); 102 103 return 0; 104 }
1 #include <stdio.h> 2 #include <string.h> 3 #include <unistd.h> 4 #include <netinet/in.h> 5 #include <arpa/inet.h> 6 7 #include "wrap.h" 8 9 #define MAXLINE 8192 10 #define SERV_PORT 8000 11 12 int main(int argc, char *argv[]) 13 { 14 struct sockaddr_in servaddr; 15 char buf[MAXLINE]; 16 int sockfd, n; 17 18 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 19 20 bzero(&servaddr, sizeof(servaddr)); 21 servaddr.sin_family = AF_INET; 22 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 23 servaddr.sin_port = htons(SERV_PORT); 24 25 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 26 27 while (fgets(buf, MAXLINE, stdin) != NULL) { 28 Write(sockfd, buf, strlen(buf)); 29 n = Read(sockfd, buf, MAXLINE); 30 if (n == 0) { 31 printf("the other side has been closed.\n"); 32 break; 33 } 34 else 35 Write(STDOUT_FILENO, buf, n); 36 } 37 Close(sockfd); 38 39 return 0; 40 }
三。epoll事件模型
1.边缘触发 epoll ET
Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据
2.水平触发 epoll LT
Level Triggered (LT) 水平触发只要有数据都会触发
//基于非阻塞的epoll et触发模型
1 #include <stdio.h> 2 #include <string.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include <sys/wait.h> 6 #include <sys/types.h> 7 #include <sys/epoll.h> 8 #include <unistd.h> 9 #include <fcntl.h> 10 11 #define MAXLINE 10 12 #define SERV_PORT 8000 13 14 int main(void) 15 { 16 struct sockaddr_in servaddr, cliaddr; 17 socklen_t cliaddr_len; 18 int listenfd, connfd; 19 char buf[MAXLINE]; 20 char str[INET_ADDRSTRLEN]; 21 int efd, flag; 22 23 listenfd = socket(AF_INET, SOCK_STREAM, 0); 24 25 bzero(&servaddr, sizeof(servaddr)); 26 servaddr.sin_family = AF_INET; 27 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); 28 servaddr.sin_port = htons(SERV_PORT); 29 30 bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 31 32 listen(listenfd, 20); 33 34 /////////////////////////////////////////////////////////////////////// 35 struct epoll_event event; 36 struct epoll_event resevent[10]; 37 int res, len; 38 39 efd = epoll_create(10); 40 41 event.events = EPOLLIN | EPOLLET; /* ET 边沿触发,默认是水平触发 */ 42 43 //event.events = EPOLLIN; 44 printf("Accepting connections ...\n"); 45 cliaddr_len = sizeof(cliaddr); 46 connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); 47 printf("received from %s at PORT %d\n", 48 inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), 49 ntohs(cliaddr.sin_port)); 50 51 flag = fcntl(connfd, F_GETFL); /* 修改connfd为非阻塞读 */ 52 flag |= O_NONBLOCK; 53 fcntl(connfd, F_SETFL, flag); 54 55 event.data.fd = connfd; 56 epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); //将connfd加入监听红黑树 57 while (1) { 58 printf("epoll_wait begin\n"); 59 res = epoll_wait(efd, resevent, 10, -1); //最多10个, 阻塞监听 60 printf("epoll_wait end res %d\n", res); 61 62 if (resevent[0].data.fd == connfd) { 63 while ((len = read(connfd, buf, MAXLINE/2)) >0 ) //非阻塞读, 轮询 64 write(STDOUT_FILENO, buf, len); 65 } 66 } 67 68 return 0; 69 }
原文:https://www.cnblogs.com/sclu/p/11306061.html