epoll 总结
epoll概述
在网络通信中,由于网络环境复杂、程序的健壮性不佳等因素的存在,会造成通信双方不能正常通信。
常见的不稳定因素如下:
1)服务器进程终止。客户端在不知道服务器进程已终止的情况下发送数据给服务器。如果客户端程序
没有处理SIGPIPE信号,客户端程序会崩溃。
2)客户端与服务器的网络不通。客户端发送的数据可能会超时返回,如果客户端提前知道连接不通则
可以立即返回。
IO复用的作用就是告知系统内核一旦发现进程指定的一个或多个IO条件就绪就通知进程,进程再进行处
理。
因此,在网络通信中使用IO复用技术很重要。
epoll属于IO复用的一种,在高并发中常用epoll+non-blocking+aio(异步)+线程池的组合的方式
来使用,从而产生强大的并发威力。
在下一篇中我会介绍 Nginx高并发实现的相关技术。本文将说明epoll+nonblocking 的使用方式。
epoll接口说明
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
该系统调用对文件描述符epfd引用的epoll实例执行控制操作。 它要求对目标文件描述符fd执行op操作。
参数说明
op
EPOLL_CTL_ADD
在文件描述符epfd引用的epoll实例上注册目标文件描述符fd,并将事件event与链接到fd的内部文件相关联。
EPOLL_CTL_MOD
更改与目标文件描述符fd关联的事件event。
EPOLL_CTL_DEL
从epfd引用的epoll实例中删除(注销)目标文件描述符fd。 该事件被忽略并可以为NULL。
event 相关类型的定义
event参数描述链接到文件描述符fd的对象。 结构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结构体中的events参数如下:
EPOLLIN
关联的文件可用于read操作。
EPOLLOUT
关联的文件可用于write操作。
EPOLLRDHUP(自Linux 2.6.17开始)
流套接字对等关闭连接,或关闭写入连接的一半。 (当使用边缘触发监控时,该标志对编写
简单的代码来检测对等关闭特别有用。)
EPOLLPRI
紧急数据可用于读取操作。
EPOLLERR
相关文件描述符发生错误情况。 epoll_wait会一直等待这个事件; 没有必要将其设置为
事件。
EPOLLHUP
挂起事件发生在关联的文件描述符上。 epoll_wait会一直等待这个事件; 没有必要将其
设置为事件。
EPOLLET
设置关联文件描述符的边缘触发行为。 epoll的默认行为是Level Triggered。 有关
Edge和Level Triggered事件分发体系结构的更多详细信息。
EPOLLONESHOT(自Linux 2.6.2开始)
设置关联文件描述符的一次性行为。 这意味着在事件被epoll_wait拉出之后,关联的文件描述符会
在内部被禁用,并且epoll接口不会报告其他事件。
用户必须使用EPOLL_CTL_MOD调用epoll_ctl()以使用新的事件掩码重新装入文件描述符。
epoll_ctl常见返回结果及错误
成功时,epoll_ctl()返回零。 当发生错误时,epoll_ctl()返回-1,并且适当地设置errno。
epoll_ctl常见返回错误
EBADF
epfd或fd不是有效的文件描述符。
EEXIST
是EPOLL_CTL_ADD,并且提供的文件描述符fd已经在此epoll实例中注册。
EINVAL
epfd不是epoll文件描述符,或者fd与epfd相同,或者所请求的操作操作不受此接口支持。
ENOENT
op是EPOLL_CTL_MOD或EPOLL_CTL_DEL,并且fd未在此epoll实例中注册。
ENOMEM
没有足够的内存来处理请求的操作控制操作。
ENOSPC
在尝试在epoll实例上注册(EPOLL_CTL_ADD)新文件描述符时遇到
/proc/sys/fs/epoll/max_user_watches所施加的限制。 更多细节见epoll(7)。
EPERM 目标文件fd不支持epoll。
epoll 测试代码
服务器端代码
代码来自以下路径:
https://github.com/eklitzke/epollet
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define MAXEVENTS 64
#define PORT 9000
static void set_nonblocking(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) {
perror("fcntl()");
return;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
perror("fcntl()");
}
}
int main(int argc, char **argv) {
// create the server socket
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("socket()");
return 1;
}
int enable = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) ==
-1) {
perror("setsockopt()");
return 1;
}
// bind
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_port = htons(PORT);
if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("bind()");
return 1;
}
// 设置socket nonblocking, listen
set_nonblocking(sock);
if (listen(sock, SOMAXCONN) < 0) {
perror("listen()");
return 1;
}
// 创建 epoll socket
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1()");
return 1;
}
// 设置 server socket for reading, 设置edge-triggered
//要使用边缘触发轮询,您必须将文件描述符置于非阻塞模式。 然后你必须调用读或写,直到他们每次都返回EWOULDBLOCK。
struct epoll_event event;
memset(&event, 0, sizeof(event));
event.data.fd = sock;
event.events = EPOLLIN | EPOLLET;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &event) == -1) {
perror("epoll_ctl()");
return 1;
}
struct epoll_event *events = calloc(MAXEVENTS, sizeof(event));
for (;;) {
//epoll阻塞等待客户端发起连接
int nevents = epoll_wait(epoll_fd, events, MAXEVENTS, -1);
if (nevents == -1) {
perror("epoll_wait()");
return 1;
}
for (int i = 0; i < nevents; i++) {//循环读取事件
if ((events[i].events & EPOLLERR) || (events[i].events & EPOLLHUP) ||
(!(events[i].events & EPOLLIN))) {
// error case
fprintf(stderr, "epoll error\n");
close(events[i].data.fd);
continue;
} else if (events[i].data.fd == sock) {
// server socket; call accept as many times as we can
for (;;) {
struct sockaddr in_addr;
socklen_t in_addr_len = sizeof(in_addr);
//与客户端建立连接
int client = accept(sock, &in_addr, &in_addr_len);
if (client == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// we processed all of the connections
break;
} else {
perror("accept()");
return 1;
}
} else {
printf("accepted new connection on fd %d\n", client);
set_nonblocking(client);
event.data.fd = client;
event.events = EPOLLIN | EPOLLET;
//客户端和服务器端建立链接后,服务器端把建立链接的描述符加
//入事件
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client, &event) == -1) {
perror("epoll_ctl()");
return 1;
}
}
}
} else {
// client socket; read as much data as we can
char buf[1024];
for (;;) {
//读取已建立链接的用户的数据
ssize_t nbytes = read(events[i].data.fd, buf, sizeof(buf));
if (nbytes == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
printf("finished reading data from client\n");
break;
} else {
perror("read()");
return 1;
}
} else if (nbytes == 0) {
//客户端断开链接
printf("finished with %d\n", events[i].data.fd);
close(events[i].data.fd);
break;
} else {
fwrite(buf, sizeof(char), nbytes, stdout);
}
}
}
}
}
return 0;
}
客户端代码
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
void str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
while (fgets(sendline, MAXLINE, fp) != NULL) {
writen(sockfd, sendline, strlen(sendline));
}
}
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(9000);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
参考资料
[1]. http://man7.org/linux/man-pages/man7/epoll.7.html
[2]. https://eklitzke.org/blocking-io-nonblocking-io-and-epoll