多路復用之epoll
作為多路復用中最高效的I/O,epoll有著select和poll都不具有的很多能力。
不同于poll和select,epoll它用三個函數來實現多路復用這一個功能。
#include <sys/epoll.h>
int epoll_create(int size);
//用于創建一個epoll模式的存儲空間,返回值是一個文件描述符,后面和函數中
//都會用到這個epoll_fd。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//epoll_ctl用于添加一個事件到epfd中,op表示方式有EPOLL_ADD,EPOLL_DEL
//EPOLL_MOD方式,fd表示你要添加進去的文件描述符,后面是一個結構體指針,
//結構體在下面會說到。
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
//epoll_wait用于等待事件的發生,這個結構體指針會儲存返回來的fd,maxevents
//表示最大能夠接收到的fd的個數,注意能接收到的fd的個數很多(查閱一些資料這個數據
//在1G內存的機子上大約能有10萬余個)。
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 */
};
//這個結構體中包含一個聯合體和一個events,events是用來描述觸發狀態,
//可以設置為EPOLLIN,EPOLLOUT,EPOLLD等。
//聯合體中我們關注fd和*ptr因為聯合體有時候會有bug產生,如果我們用str來
//存放讀取的數據的時候,我們可以讓這個ptr指向一個結構體,結構體中設置
//fd和*buf參數。epoll之所以比之前的多路復用高效主要原因有以下幾個
1>:epoll的組織方式,它是以兩個一個很高效的結構體紅黑樹,list鏈表
紅黑樹用于存放fd,一旦有事件發生它能夠以O(1)的時間復雜度找到并且
把它放到list中,這樣發回值就是這個event結構體指針,它里面存放的
便是發生事件的fd,從之前多路復用的輪訓O(N),減少到O(1),可見它的
高效之處。
2>:觸發方式:epoll可以使用兩種觸發方式來獲取事件;下面會說到的水平
觸發和邊緣觸發。使用邊緣觸發方式可以使epoll更加高效。
邊緣觸發和水平觸發
水平觸發PT:epoll_wait一旦fd中發生狀態變化假設狀態變化是read,并且如果沒有讀完,下次會繼續提醒,直到把緩沖區fd中的數據讀完為止。
邊緣觸發ET:epoll_wait當fd中狀態發生變化時假設狀態變化是read,它會在第一次提醒,如果沒有把它讀完則后面不會再提醒,除非有新的數據到來才會接著上次的往后面讀。
在epoll_wait下要將套接字用fcntl函數設置為非阻塞,為什么要設置未非阻塞呢?想了好久的我終于發現,在一次ET中因為并不保證把緩沖區中的數據徹底讀完,而阻塞模式下的sock是要保證把fd中的數據讀完的,兩者矛盾,會導致不接受新到來的fd。
ET需要用到的是自定義的read函數,如下所示:
57 int read_fd(int sock,char *buf,int size)
58 {
59 int _size=-1;
60 int index=0;
61 while((_size=read(sock,buf+index,size-index)))
62 {
63 if(_size<0&&errno==EAGAIN)
64 {
65 break;
66 }
67 index+=_size;
68 _size=-1;
69 }
70 return index;
71 }
//因為ET模式的特點必須保證一次把整個sock中的一次數據全部讀完,不然如果沒有下次的數據
//到來,前面沒有讀完的數據就會永久性的丟失了。
//當read讀到sock中整個數據流的最末尾的時候會產生一個類似errno的信號EAGAIN告訴它已經
//讀到了sock中的最后一個數據。下面是一個epollET模式下的client與server
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/socket.h>
4 #include<sys/types.h>
5 #include<arpa/inet.h>
6 #include<netinet/in.h>
7 #include<sys/epoll.h>
8 #include<fcntl.h>
9 #include<errno.h>
10 #include<string.h>
11 #include<unistd.h>
12
13 #define _MAX_LISTEN_ 6
14 #define _MAX_EPFD_ 64
15 #define _MAX_BUF_ 1024
16
17 void nonblock(int sock)
18 {
19 int fl=fcntl(sock,F_GETFL);
20 if(fl<0)
21 {
22 perror("fcntl");
23 exit(2);
24 }
25 if(fcntl(sock,F_SETFL,fl|O_NONBLOCK)<0)
26 {
27 exit(3);
28 }
29 }
30 int Listensock(char *ip,int port)
31 {
32 int sock=socket(AF_INET,SOCK_STREAM,0);
33 if(sock<0)
34 {
35 perror("socket");
36 exit(1);
37 }
38 nonblock(sock);
39 struct sockaddr_in local;
40 local.sin_addr.s_addr=inet_addr(ip);
41 local.sin_port=htons(port);
42
43 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
44 {
45 perror("bind");
46 }
47
48 if(listen(sock,_MAX_LISTEN_)<0)
49 {
50 perror("listen");
51 } }
52
53 return sock;
54
55 }
56
57 int read_fd(int sock,char *buf,int size)
58 {
59 int _size=-1;
60 int index=0;
61 while((_size=read(sock,buf+index,size-index)))
62 {
63 if(_size<0&&errno==EAGAIN)
64 {
65 break;
66 }
67 index+=_size;
68 _size=-1;
69 }
70 return index;
71 }
72 void epollserver(int sock)
73 {
74 int epfd=epoll_create(256);
75
76 if(epfd<0)
77 {
78 perror("epoll_create");
79 exit(4);
80 }
81
82 struct epoll_event ev;
83 ev.data.fd=sock;
84 ev.events=EPOLLIN|EPOLLET;
85
86 if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&ev)<0)
87 {
88 perror("epoll_ctl");
89 exit(5);
90 }
91 struct epoll_event epfds[_MAX_EPFD_];
92
93 int fd=-1;
94 int i;
95 for(i=0;i<_MAX_EPFD_;i++)
96 {
97 epfds[i].data.fd=fd;
98 }
99 int timeout=5000;
100
101 int fdlen=0;
102 while(1)
103 {
104 switch(fdlen=epoll_wait(epfd,epfds,_MAX_EPFD_,timeout))
105 {
106 case -1:
107 {
108 perror("epoll_wait");
109 continue;
110 }
111 case 0:
112 {
113 printf("timeout\n");
114 }
115 default:
116 {
117 struct sockaddr_in client;
118 int client_len=sizeof(client);
119 int i=0;
120 for(i=0;i<fdlen;i++)
121 {
122 int retfd=epfds[i].data.fd;
123 if(retfd==sock&&(epfds[i].events&EPOLLIN))
124 {
125 int recvfd=accept(sock,(struct sockaddr*)\
126 &client,&client_len);
127 if(recvfd<0)
128 {
129 continue;
130 }
131 else
132 {
133 nonblock(recvfd);
134 ev.data.fd=recvfd;
135 ev.events=EPOLLIN|EPOLLET;
136 if(epoll_ctl(epfd,EPOLL_CTL_ADD,recvfd,&ev)<0)
137 {
138 perror("epoll_ctl");
139 continue;
140 }
141 printf("a client come..ip=%s\n",\
142 inet_ntoa(client.sin_addr));
143
144 }
145 }
146 else if (epfds[i].events&EPOLLIN)
147 { {
148 char buf[_MAX_BUF_];
149 memset(buf,'\0',_MAX_BUF_);
150 int ret=read_fd(retfd,buf,_MAX_BUF_);
151 if(ret>0)
152 {
153 buf[ret-1]='\0';
154 printf("client ::%s\n",buf);
155 fflush(stdout);
156 }else if(ret==0){
157 printf("ip=%s client is leave...\n",\
158 inet_ntoa(client.sin_addr));
159 }else{
160 //doing noting
161 }
162 }//else{此處可以改成回顯}
163 }
164 }
165
166 }
167 }
168
169 int main(int argc,char *argv[])
170 {
171 if(argc!=3) if(argc!=3)
172 {
173 printf("[%s][ip][port]\n",argv[0]);
174 }
175 char *ip=argv[1];
176 int port=atoi(argv[2]);
177 int sock=Listensock(ip,port);
178
179 int opt=1;
180
181 if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
182 {
183 perror("setsockopt");
184 }
185
186 epollserver(sock);
187
188 return 0;
189 }
改為回顯模式:
當讀完client的數據后,可以將event改為EPOLLOUT然后將讀到的數據保存起來
當下次寫事件發生時,將保存的數據扔出去。
注意:因為結構體中的data是一個聯合體,當我們存放完fd后再去存放ptr有可能
會有bug,這里的方法是讓ptr指向一個結構體,這個結構體中保存著fd和buf。
typedef struct p_buf{
int fd;
char outbuf[_MAX_BUF_];
}p_buf;
//自定義的緩沖區
164 else if (epfds[i].events&EPOLLIN)
166 {
167 ptrbuf *p_buf=(ptrbuf *)malloc(sizeof(ptrbuf));
//將數據直接讀到自定義的緩沖區中
168 memset(p_buf->outbuf,'\0',sizeof(p_buf->outbuf));
169 p_buf->fd=retfd;
170 int ret=read_fd(retfd,p_buf->outbuf,_MAX_BUF_);
171
172 if(ret>0)
173 {
174 p_buf->outbuf[ret-1]='\0';
175 printf("client ::%s\n",p_buf->outbuf);
176 fflush(stdout);
177 ev.events=EPOLLOUT|EPOLLET;
178 ev.data.ptr=p_buf;
//設置為EPOLLOUT模式
179 if(epoll_ctl(epfd,EPOLL_CTL_MOD,retfd,&ev)<0)
180 {
181 perror("epoll_ctl");
182 continue;
183 }
184 }else if(ret==0){
185 if(epoll_ctl(epfd,EPOLL_CTL_DEL,retfd,NULL)<0)
186 {
187 perror("epoll_ctl");
188 }
189 printf("ip=%s client is leave...\n",\
190 inet_ntoa(client.sin_addr));
191 }else{
192 //doing noting
193 }
194 }else{//當寫條件滿足時,回顯消息并且改回為EPOLLIN模式
195 ptrbuf* outptr=(ptrbuf*)epfds[i].data.ptr;
196 int outfd=outptr->fd;
197 outptr->outbuf[strlen(outptr->outbuf)]='\n';
198 out_write(outfd,outptr->outbuf,_MAX_BUF_);
199 free(outptr);
200 ev.events=EPOLLIN|EPOLLET;
201 ev.data.fd=outfd;
202 if(epoll_ctl(epfd,EPOLL_CTL_MOD,outfd,&ev)<0)
203 {
204 perror("epoll_ctl");
205 }
206 }
207 }
208 }
209 }
210 }
211 }
212
194,6-24 96% 183,8-32 81%回顯模式:

總結:
epoll對比之前的select和poll都有不小的改進,不用遍歷整個buf,沒有大小限制,并且有不同的模式可以選擇,效率之高可想而知。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。