1.什么是epoll
epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll,目的是为了得到已经就绪的文件描述符,当时间就绪是按顺序添加进去。
epoll的相关系统调用
epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调用。
1>. int epoll_create(int size);
创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
typedef union epoll_data
{
void* ptr;
int fd;
_uint32_t u32;
_uint64_t u64
}epoll_data_t;
//感兴趣的事件和被触发的事件
struct epoll_event
{
epoll_data_t data;
_uint32_t events;
}
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level
Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个
socket的话,需要再次把这个socket加入到EPOLL队列里
3> int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组,
epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复
制到这个events数组中,不会去帮助我们在用户态中分配内存)。maxevents告之内核这个
events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时
时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,
返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
2.epoll工作原理
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
3.epoll的2种工作方式-水平触发(LT)和边缘触发(ET)
水平触发(LT):
以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll,并且无论后面的数据是否被使用。
LT是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法
中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果
你不作任何操作,内核还是会继续通知你的。
边缘触发(ET):只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件.即只会在数据到来时通知一次。
epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死.
I.基于非阻塞文件句柄.
II. 只有当read或者write返回EAGAIN时才需要挂起,等待。但这并不是说每次read()
时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。
4.具体代码实现:
//epoll_server.c
1 #include<stdio.h>
2 #include<arpa/inet.h>
3 #include<sys/socket.h>
4 #include<sys/epoll.h>
5 #include<stdlib.h>
6 #include<errno.h>
7 #include<netinet/in.h>
8 #include<string.h>
9 #include<assert.h>
10 #include<fcntl.h>
11 #include<errno.h>
12 #define _MAX_ 20
13
14 typedef struct data_buf
15 {
16 int fd;
17 char buf[1024];
18 }data_buf_t,*data_buf_p;
19 static void SetNonBlock(int fd)
20 {
21 int fds=0;
22 if((fds=fcntl(fd,F_GETFL))<0)
23 {
24 perror("fcntl");
25 exit(1);
26 }
27 if(fcntl(fd,F_SETFL)<0)
28 {
29 perror("fcntl");
30 exit(2);
31 }
32
33
34
35 }
36 static void usage(const char*proc)
37 {
38 printf("usage: %s [ip] [port]",proc);
39
40 }
42 static int startup(int port,char*ip)
43 {
44 int sock=socket(AF_INET,SOCK_STREAM,0);
45 if(sock<0)
46 {
47 perror("socket");
48 exit(1);
49 }
50 struct sockaddr_in local;
51 local.sin_family=AF_INET;
52 local.sin_port=htons(port);
53 local.sin_addr.s_addr=inet_addr(ip);
54 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
55 {
56 perror("bind");
57 exit(2);
58 }
59 if(listen(sock,5)<0)
60 {
61 perror("listen");
62 exit(2);
63 }
64 return sock;
65
66 }
67
68 static int read_data(int fd,char*buf,int size)//什么时候读完用EADAIN检测
69 {
70 printf("read_data\n");
71 assert(buf);
72 int index=0;
73 ssize_t ret=-1;
74 while(ret=read(fd,buf+index,size-index))
75 {
76 if(errno==EAGAIN)//客户端无数据发送
77 {
78 break;
79 }
80 index+=ret;
81
82 }
83
84 //printf("read_data\n");
85 return index;
86 }
87
88 static void write_data(int fd,char *buf)//什么时候写完
89
90 {
91
92 ssize_t len=strlen(buf);
93 int index=0;
94 int ret=0;
95 do
96 {
97 ret=write(fd,buf+index,len-index);
98 index+=ret;
99
100 }while(ret>0);
101
102 }
103 static int epoll_server(int sock)
104 {
105 //1.chuangjian
106 int epoll_fd= epoll_create(256);//创建epoll
107 if(epoll_fd<0)
108 {
109 perror("epoll_create");
110 return 1;
111 }
112 struct sockaddr_in client;
113 socklen_t len=sizeof(client);
114 //注册套接字信息
115 struct epoll_event ev;
116 ev.events=EPOLLIN;
117 ev.data.fd=sock;
118 SetNonBlock(sock);//将套接字设置为非阻塞
119 epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev);//将套接字添加到epoll
120 struct epoll_event ret_ev[_MAX_];
121 int ret_num=_MAX_;
122 int timeout=8000;
123 int ready_num=-1;
124 while(1)
125 {
//有多少准备就绪
126 switch(ready_num=epoll_wait(epoll_fd,ret_ev,ret_num,timeout))
127 {
128 case -1://error
129 perror("epoll_wait");
130 break;
131 case 0://timeout
132 printf("timeout\n");
133 break;
134 default:
135 {
136 int i=0;
137 for(;i<ready_num;++i)
138 {
//为listen_sock
140 if((ret_ev[i].data.fd==sock)&&(ret_ev[i].events&EPOLLIN))
141 {
142 int fd=ret_ev[i].data.fd;
143
144 int new_sock=accept(sock,(struct sockaddr*)&client,&len);
145 if(new_sock<0)
146 {
147 perror("accept");
148 continue;
149 }
150
151 printf("get connect...\n");
152 //将新的套接字添加到epoll
153 ev.events=EPOLLIN|EPOLLET;
154 ev.data.fd=new_sock;
155 epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
156
157 }
158
159 else if(ret_ev[i].events&EPOLLIN)
160 {
162 //可读就绪
163 int fd=ret_ev[i].data.fd;
164 // printf("%d\n",fd);
165 data_buf_p mem=(data_buf_p)malloc(sizeof(data_buf_t));
166 mem->fd=fd;
167
168 ssize_t s=read(mem->fd,mem->buf,sizeof(mem->buf)-1);
169 //ssize_t s=read_data(mem->fd,mem->buf,sizeof(mem->buf)-1) ;
170 printf("%d\n",s);
171 if(s>0)
172 {
173
174 mem->buf[s]=‘\0‘;
175 printf("client:%s\n",mem->buf);
176 //epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
177 ev.events=EPOLLOUT|EPOLLET;
178 ev.data.ptr=mem;
179 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);//将其修改为关心写
180 }
181
182 else if(s==0)
183 {
184 close(fd);
185 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);//删除套接字
186 free(mem);
187 }
188 else
189 {
190
191 printf("error\n");
192 }
193 }
194 //写就绪
195 else if(ret_ev[i].events&EPOLLOUT)
196 {
197 data_buf_p mem=(data_buf_p)ret_ev[i].data.ptr;
205 char*msg="HTTP/1.0 200 OK\r\n\r\nhello bit :)\r\n";
206
207 write_data(mem->fd,msg);
208 close(mem->fd);
212 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NULL);
213 free(mem);
214 }
215
216 }
217 }
218 break;
219 }
220 }
221 }
222 int main(int argc,char*argv[])
223 {
224 if(argc!=3)
225 {
226 usage(argv[0]);
227 return 1;
228 }
229 int port=atoi(argv[2]);
230 char* ip=argv[1];
231 int listen_sock=startup(port,ip);
232 int opt=1;//端口复用
233 setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)) ;
234 epoll_server(listen_sock);
235 close(listen_sock);
236
237 return 0;
238
239 }
//epoll_client.c
1 #include<stdio.h>
2 #include<errno.h>
3 #include<poll.h>
4 #include<string.h>
5 #include<sys/types.h>
6 #include<arpa/inet.h>
7 #include<netinet/in.h>
8 #include<sys/socket.h>
9 #include<stdlib.h>
10
11 static void usage(char *proc)
12 {
13 printf("usage: %s [ip] [port] ",proc);
14 }
15
16
17 int main(int argc, char*argv[])
18 {
19 if(argc!=3)
20 {
21 usage(argv[0]);
22 return 1;
23 }
24 int port=atoi(argv[2]);
25 char* ip=argv[1];
26 int sock=socket(AF_INET,SOCK_STREAM,0);
27 if(sock<0)
28 {
29 perror("socket");
30 exit(1);
31 }
32 struct sockaddr_in remote;
33 remote.sin_family=AF_INET;
34 remote.sin_port=htons(port);
35 remote.sin_addr.s_addr=inet_addr(ip);
36 int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote));
37 if(ret<0)
38 {
39 perror("coonect");
40 exit(3);
41
42 }
43 char buf[1024];
44 while(1)
45 {
46 memset(buf,‘\0‘,sizeof(buf));
47 printf("please enter: ");
48 fflush(stdout);
49 ssize_t s=read(0,buf,sizeof(buf)-1);
50 if(s>0)
51 {
52 write(sock,buf,strlen(buf));
53
54 }
//回显消息
55 ssize_t size=read(sock,buf,sizeof(buf));
56 printf("server: %s\n",buf);
57
58 }
59 close(sock);
60 return 0;
61 }使用客户端运行结果:
使用浏览器运行结果:
本文出自 “输出菱形图案” 博客,请务必保留此出处http://10541571.blog.51cto.com/10531571/1786668
原文:http://10541571.blog.51cto.com/10531571/1786668