1 Epoll编程要点
1.1 连接建立
服务器接收客户端连接:
- 接收过程分为两个步骤首先是服务器注册监听listenfd;
- 第二步是服务接收客户端连接clientfd;
- 所有的socket都是利用epoll_ctl交由epoll进行管理
epfd = epoll_create(10);
// 1. 注册监听 listenfd 的读事件
struct epoll_event ev = {0, {0}};
ev.events |= EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
// 2. 当触发 listenfd 的读事件,调用 accept 接收新的连接
int clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &len));
struct epoll_event ev = {0, {0}};
ev.events |= EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
客户端连接服务器:
- 客户端连接服务器成功需要客户端发送ack成功才能成功,即写事件EPOLLOUT被触发。
epfd = epoll_create(10);
//创建客户端socket并连接服务器
int connectfd = socket(AF_INET, SOCK_STREAM, 0);
connect(connectfd, (struct sockaddr *)&addr, sizeof(addr));
// 2. 注册监听 connectfd 的写事件
struct epoll_event ev = {0, {0}};
ev.events |= EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_ADD, connectfd, &ev);
// 3. 当 connectfd 写事件被触发,连接建立成功
if (status == CONNECTING&& e->events & EPOLLOUT) {
status == CONNECTED;
}
1.2 连接断开
- 只有两种模式一种是EPOLLRDHUP读端关闭;
- 另一种是EPOLLHUP读写端都关闭
if (e->events & EPOLLRDHUP) {
// 读端关闭
read_close(fd);//可以进行写相关操作,之后close(fd);
}
if (e->events & EPOLLHUP) {
// 读写端都关闭
close(fd);
}
被动断开:
- 对于需要进行半连接操作的,可以利用读端或者写端关闭;操作对于的写端和读端
- read返回值为0 为读端被动关闭;
- write返回值为-1 同时errno数值为EPIPE为写端被动关闭;
//接收端读端被动关闭;发送端写端关闭。
int n = read(fd, buf, size);
if (n == 0) {
read_close(fd);//可以进行写相关操作,之后close(fd);
}
//被动写端关闭
int n = write(fd, buf, size);
if (n == -1 && errno == EPIPE) {
write_close(fd);//可以进行读相关操作,之后close(fd);
}
1.3 数据到达
- 消息到达需要忽略EINTR系统中断错误;
- 消息到达需要忽略EWOULDBLOCK接收缓冲区为空的错误,当没有数据需要退出循环读数据;
- 其他错误需要:调用close关闭连接
- 数据到达对于水平触发和边沿触发存在差异需要分别考虑
while(1)
{
int n = read(fd, buf, size);
if (n < 0) { // n == -1
if (errno == EINTR)
continue;
if(errno == EWOULDBLOCK)
break;
close(fd);
} else if (n == 0) {
close(fd);
} else {
// 处理接收数据
}
}
1.4 数据发送完毕
- EINTR系统中断直接返回;等待下一次触发写;
- EWOULDBLOCK写缓冲区满返回;写失败需要再次添加写事件等待下一次可写触发。
- wirte写完数据后需要将写事件删除。防止一直触发可写事件。
- 数据发送对于水平触发和边沿触发存在差异需要分别考虑
while(1)
{
int n = write(fd, buf, size);
if (n == -1) {
if (errno == EINTR {
continue;
}
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
if (errno == EWOULDBLOCK) {
struct epoll_event ev = {0, {0}};
ev.events = EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
break;
}
close(fd);
}else if (n > 0) {
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
}
}
1.5 epoll事件处理
- epoll_wait设置的MAX_EPOLL_LENGTH大小是就绪队列的大小,决定的是单次可以接收的最大就绪事件,对于整体性能影响不大;
- 最后超时事件可以根据业务设置
while (1) {
int nready = epoll_wait(re->epfd, events, MAX_EPOLL_LENGTH, 1000);
if (nready < 0) {
printf("epoll_wait error, exit\n");
continue;
}
//事件处理
}