Epoll
epoll操作由三个函数构成:头文件为 #include <sys/epoll.h>
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
1. epoll_create函数
函数声明:int epoll_create(int size)
创建一个epoll的句柄(文件描述符),size用来告诉内核这个epoll句柄能监听的socket fd个数。需要注意的是,当创建好epoll句柄后,它就会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
2. epoll_ctl函数
函数声明:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个epoll文件描述符上的事件,可以注册事件,修改事件,删除事件。
参数:
epfd:由 epoll_create 生成的epoll专用的文件描述符;
op:要进行的操作例如注册事件,可能的取值EPOLL_CTL_ADD 注册、EPOLL_CTL_MOD 修 改、EPOLL_CTL_DEL 删除
fd:关联的文件描述符;
event:指向epoll_event的指针;
第四个参数的数据结构:
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 */
};
如:
struct epoll_event ev;
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
- EPOLLIN :表示对应的文件描述符可以读;
- EPOLLOUT:表示对应的文件描述符可以写;
- EPOLLPRI:表示对应的文件描述符有紧急的数据可读
- EPOLLERR:表示对应的文件描述符发生错误;
- EPOLLHUP:表示对应的文件描述符被挂断;
- EPOLLET:表示对应的文件描述符有事件发生;
3. epoll_wait函数
函数声明:int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
该函数用于轮询I/O事件的发生;
参数:
epfd:由epoll_create 生成的epoll专用的文件描述符;
epoll_event:用于回传代处理事件的数组;
maxevents:每次能处理的事件数;
timeout:等待I/O事件发生的超时值(单位ms);-1相当于阻塞,0相当于非阻塞。
return:返回在规定的时间内获取到IO数据的个数
epoll的工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
测试代码:
服务器代码:server.cpp
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <netinet/in.h>
12 #include <sys/types.h>
13 #include <sys/socket.h>
14 #include <unistd.h>
15 #include <sys/epoll.h>
16 #include <arpa/inet.h>
17 #include <iostream>
18 #include <netdb.h>
19
20 #define MAX_DATA_SIZE 4096
21 #define SERVER_PORT 8000
22 #define CON_QUEUE 20
23 #define MAX_EVENTS 10
24
25 void acceptConn(int sockfd, int epollfd);
26 void recvHandle(int clientfd);
27
28 int main()
29 {
30 int sockfd;
31 struct sockaddr_in servaddr;
32
33 if((sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
34 {
35 perror("创建socket错误");
36 exit(-1);
37 }
38
39 //设置套接口为可重用状态
40 int reuse = 1; //0关闭 ,1 开启
41 if(-1 == ::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)))
42 {
43 perror("不能设置套接口为可重用状态");
44 ::close(sockfd);
45 exit(-1);
46 }
47 //设置套接口发送接收缓冲,并且服务器的必须在accept之前设置
48 socklen_t recv_size = 4 * 1024;
49 if(-1 == ::setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recv_size, sizeof(recv_size)))
50 {
51 perror("设置套接口接收缓冲错误");
52 ::close(sockfd);
53 exit(-1);
54 }
55 if(-1 == ::setsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &recv_size, sizeof(recv_size)))
56 {
57 perror("设置套接口接收缓冲错误");
58 ::close(sockfd);
59 exit(-1);
60 }
61
62 bzero(&servaddr, sizeof(servaddr));
63 servaddr.sin_family = AF_INET;
64 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
65 servaddr.sin_port = htons(SERVER_PORT);
66
67 int ret = ::bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
68 if(-1 == ret)
69 {
70 perror("bind 错误");
71 ::close(sockfd);
72 exit(-1);
73 }
74 if(::listen(sockfd,CON_QUEUE) == -1)
75 {
76 perror("监听失败!");
77 ::close(sockfd);
78 exit(-1);
79 }
80
81 //epoll 初始化
82 int epollfd; //epoll 描述符
83 struct epoll_event eventlist[MAX_EVENTS];
84
85 epollfd = epoll_create(1);
86 struct epoll_event event;
87 event.events = EPOLLIN|EPOLLET;
88 event.data.fd = sockfd;
89 if(epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) < 0)
90 {
91 printf("epoll 加入失败 fd:%d\n",sockfd);
92 exit(-1);
93 }
94
95 printf("server init success!\n");
96 while(1)
97 {
98 int timeout = 10000;
99
100 //epoll_wait处理
101 //ret会返回在规定的时间内获取到IO数据的个数,并把获取到的event保存在eventList中,注意在每次执行该函数时eventList都会清空,由epoll_wait函数填写。
102 //而不清除已经EPOLL_CTL_ADD到epollfd描述符的其他加入的文件描述符。
103 //epoll里面的文件描述符要手动通过EPOLL_CTL_DEL进行删除。
104 int ret = epoll_wait(epollfd, &eventlist[0], MAX_EVENTS, timeout);
105 if(ret < 0)
106 {
107 perror("epoll error\n");
108 break;
109 }
110 else if(ret == 0)
111 {
112 //超时
113 printf("epoll_wait 等待超时\n");
114 continue;
115 }
116 printf("ret = %d\n",ret);
117 for(int i = 0; i < ret; ++i)
118 {
119 if(eventlist[i].events & EPOLLERR || !(eventlist[i].events & EPOLLIN))
120 {
121 printf("epoll error");
122 ::close(eventlist[i].data.fd);
123 exit(-1);
124 }
125
126 if(eventlist[i].data.fd == sockfd)
127 {
128 acceptConn(sockfd, epollfd);
129 // handle(eventlist[i].data.fd);
130 }
131
132 else if(eventlist[i].events & EPOLLIN) //接收消息
133 {
134 // acceptConn(sockfd, epollfd);
135 recvHandle(eventlist[i].data.fd);
136 }
137 }
138
139 }
140 ::close(epollfd);
141 ::close(sockfd);
142 return 0;
143 }
144
145 //接收客户端的连接,addr返回的地址,返回的客户端套接口
146 /*int accept(int sock, struct sockaddr_in *addr)
147 {
148 socklen_t len = sizeof(struct sockaddr_in);
149 bzero(addr, sizeof(struct sockaddr_in));
150 struct epoll_event ev;
151 int rc = epoll_wait(kdpfd, &ev, 1, 2000);
152 //这里kdpfd is the library function epoll_create( int size)的reture value.Here is called by epoll_create(1).You can man it.
153 if(1 == rc && (ev.events & EPOLLIN))
154 return TEMP_FAILURE_RETRY(::accept(sock, (struct sockaddr*)addr, &len));
155 return -1;
156 }*/
157
158 void acceptConn(int sockfd, int epollfd)
159 {
160 struct sockaddr_in cliaddr;
161 socklen_t len = sizeof(struct sockaddr_in);
162 bzero(&cliaddr,len);
163 int confd = ::accept(sockfd, (struct sockaddr*)&cliaddr, &len);
164 if(confd < 0)
165 {
166 printf("accept 错误\n");
167 exit(-1);
168 }
169 printf("sockfd:%d\n",sockfd);
170 /* else
171 {
172 int v = getpeername(confd,(sockaddr*) &cliaddr,&len);
173 if(v == -1)
174 std::cout<<"获取客户端的地址和端口失败!"<<std::endl;
175 std::cout<<"address:"<<inet_ntoa(cliaddr.sin_addr)<<" port:"<<htons(cliaddr.sin_port)<<std::endl;
176 int recvlen= 0;
177 char recvBuf[MAX_DATA_SIZE];
178 memset(recvBuf, 0, sizeof(recvBuf));
179 recvlen = ::recv(confd, (char*)recvBuf, MAX_DATA_SIZE, MSG_NOSIGNAL );
180 if(recvlen == 0)
181 return;
182 if(recvlen < 0)
183 {
184 printf("server recv error\n");
185 exit(-1);
186 }
187 printf("接收到的数据:%s\n",recvBuf);
188 }*/
189 struct epoll_event event;
190 event.data.fd=confd;
191 event.events=EPOLLIN;
192 epoll_ctl(epollfd,EPOLL_CTL_ADD,confd,&event);
193 }
194
195 void recvHandle(int clientfd)
196 {
197 struct sockaddr_in cliaddr;
198 socklen_t len = sizeof(struct sockaddr_in);
199 bzero(&cliaddr,len);
200
201 int v = getpeername(clientfd,(sockaddr*) &cliaddr,&len);
202 if(v == -1)
203 std::cout<<"获取客户端的地址和端口失败!"<<std::endl;
204 std::cout<<"address:"<<inet_ntoa(cliaddr.sin_addr)<<" port:"<<htons(cliaddr.sin_port)<<std::endl;
205
206 int recvlen= 0;
207 char recvBuf[MAX_DATA_SIZE];
208 memset(recvBuf, 0, sizeof(recvBuf));
209 recvlen = ::recv(clientfd, (char*)recvBuf, MAX_DATA_SIZE, MSG_NOSIGNAL );
210 if(recvlen == 0)
211 return;
212 if(recvlen < 0)
213 {
214 printf("server recv error\n");
215 exit(-1);
216 }
217 printf("接收到来自clientfd:%d的数据:%s\n", clientfd,recvBuf);
218 return;
219 }
客户端代码:client.cpp
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <netinet/in.h>
13 #include <sys/types.h>
14 #include <sys/socket.h>
15 #include <unistd.h>
16 #include <netdb.h>
17 #include <error.h>
18 #include <arpa/inet.h>
19 #include <iostream>
20
21 #define MAX_DATA_SIZE 4096
22 #define SERVER_PORT 8000
23
24 int main()
25 {
26 int sockfd;
27 struct sockaddr_in servaddr;
28 int pid;
29 char sendBuf[MAX_DATA_SIZE],revBuf[MAX_DATA_SIZE];
30 int send_size,recv_size;
31
32 sockfd = ::socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
33 bzero(&servaddr,sizeof(servaddr));
34 servaddr.sin_family = AF_INET;
35 servaddr.sin_port = htons(SERVER_PORT);
36 servaddr.sin_addr.s_addr = inet_addr("192.168.97.246");
37
38 if(::connect(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr_in)) == -1)
39 {
40 perror("connect 失败,请检查端口和ip地址!");
41 exit(-1);
42 }
43
44 while(1)
45 {
46 printf("请输入:");
47 fgets(sendBuf,MAX_DATA_SIZE,stdin);
48 send_size = ::send(sockfd, sendBuf, MAX_DATA_SIZE, MSG_NOSIGNAL);
49 if(send_size < 0)
50 {
51 perror("send error");
52 }
53 memset(sendBuf, 0, sizeof(sendBuf));
54 }
55 return 0;
56 }