Linux Epoll模型
epoll前世今生
linux系统产生了下面五种网络模式的方案:
最常见的是前三种。
1、阻塞IO(blocking IO):
对用户态应用程序来说,当调用read()系统调用的时候。如果待读取的fd没有数据的时候,就回阻塞,直到有数据回返回。
2、非阻塞IO(nonblocking IO)
对用户态程序来说,当调用read()系统调用的时候,如果待读取的fd没有数据的时候,就返回error,用户态程序可以根据错误码判断,根据程序设计选择继续循环调用,来获取数据。
3、IO多路复用(IO multiplexing)
epoll模型就是IO多路复用的一种,另外两个是select,poll。IO多路复用,多个网络连接socket复用一个processor。它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
如果处理的连接数不是很高的话,使用select/epoll的服务器不一定比使用“多线程” + “阻塞IO”的服务器性能更好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更好,而是在于性能更多的连接。
4、信号驱动IO(signal driven IO)不常用
5、异步IO (asynchronous IO)
select,poll,epoll
IO多路复用,就是一个processor,处理多个IO连接。
select:
调用select后,阻塞。直到有socket有数据的时候,返回。这个时候需要遍历所有管理的fd。通过查看是否置位的方式,查看是否有数据,如果有数据,则读取数据处理。
时间复杂度:O(n)
实现细节:
通过维护fd_set来管理待处理的fd的状态。
fd_set简单地理解为一个长度是1024的比特位,每个比特位表示一个需要处理的FD,如果是1,那么表示这个FD有需要处理的I/O事件,否则没有
缺点:
1、管理的fd有上限
2、需要便利所有fd,通过置位判断才能判断出来到底是那个fd有数据
3、用户空间和内核空间的复制非常消耗资源
poll:
和select类似,只是用于管理待处理的fd的状态,从fd_set,调整成一个链表。见实现细节。
时间复杂度:O(n)
实现细节:
其和select不同的地方:采用链表的方式替换原有fd_set数据结构,而使其没有连接数的限制。
系统调用如下:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds是一个pollfd数据结构的数组,pollfd数据结构如下。nfds代表fds的长度。
struct pollfd {
int fd; // 需要监视的文件描述符
short events; // 需要内核监视的事件
short revents; // 实际发生的事件
};
缺点;
1、需要便利所有fd,判断到底那个fd有数据,之后进行数据处理
epoll实现原理
epollevent对象是epoll管理多个网络连接的数据结构。两个重要的属性如下:
- struct list_head rdllist; 双向链表rdllist保存着将要通过epoll_wait返回给用户的、满足条件的事件。 也就是待处理事件。
- struct rb_root_cached rbr; 红黑树,存储通过epoll_ctl增删改的fd树
也就是当有fd有数据的时候,rdllist就回存储待处理的fd列表。那对于应用程序来说,就有了不需要遍历所有管理的fd来判断那些fd有数据了。解决了select/poll需要遍历fds来判断那些fd就绪了。
内核源码如下:
/*
* This structure is stored inside the "private_data" member of the file
* structure and represents the main data structure for the eventpoll
* interface.
*/
struct eventpoll {
/*
* This mutex is used to ensure that files are not removed
* while epoll is using them. This is held during the event
* collection loop, the file cleanup path, the epoll file exit
* code and the ctl operations.
*/
struct mutex mtx;
/* Wait queue used by sys_epoll_wait() */
wait_queue_head_t wq;
/* Wait queue used by file->poll() */
wait_queue_head_t poll_wait;
/* List of ready file descriptors */
struct list_head rdllist;
/* Lock which protects rdllist and ovflist */
rwlock_t lock;
/* RB tree root used to store monitored fd structs */
struct rb_root_cached rbr;
/*
* This is a single linked list that chains all the "struct epitem" that
* happened while transferring ready events to userspace w/out
* holding ->lock.
*/
struct epitem *ovflist;
/* wakeup_source used when ep_scan_ready_list is running */
struct wakeup_source *ws;
/* The user that created the eventpoll descriptor */
struct user_struct *user;
struct file *file;
/* used to optimize loop detection check */
struct list_head visited_list_link;
int visited;
#ifdef CONFIG_NET_RX_BUSY_POLL
/* used to track busy poll napi_id */
unsigned int napi_id;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
/* tracks wakeup nests for lockdep validation */
u8 nests;
#endif
};
epoll流程
服务端:
epoll_create:创建一个epollfd
epoll_ctl:添加一个fd到epollfd中,被epollfd所管理,再细化一下,会将添加的fd添加到红黑树中。
epoll_wati:阻塞等待,当待处理事件列表中存在事件的时候,返回待处理事件的个数。
进而可以对有IO的事件进行处理。
代码示例
服务端代码
#include <iostream>
#include <strings.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#define MAXLINE 10
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5555
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts = fcntl(sock, F_GETFL);
if (opts < 0)
{
perror("fcntl(sock, GETFL)");
exit(1);
}
opts = opts | O_NONBLOCK;
if (fcntl(sock, F_SETFL, opts) < 0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main()
{
int i, maxi, listenfd, connfd, sockfd, epfd, nfds;
ssize_t n;
char line[MAXLINE];
socklen_t clilen = sizeof(sockaddr_in);;
struct epoll_event ev, events[20];
epfd = epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
setnonblocking(listenfd);
ev.data.fd = listenfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr = "127.0.0.1";
inet_aton(local_addr, &(serveraddr.sin_addr));
serveraddr.sin_port = htons(SERV_PORT);
bind(listenfd, (sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for (;;)
{
nfds = epoll_wait(epfd, events, 20, 1000);
for (i = 0; i < nfds; ++i)
{
if (events[i].data.fd == listenfd)
{
connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen);
if (connfd < 0)
{
perror("connfd<0");
exit(1);
}
setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
std::cout << "connect from " << str << std::endl;
ev.data.fd = connfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
}
else if (events[i].events & EPOLLIN)
{
if ((sockfd = events[i].data.fd) < 0)
{
continue;
}
if ((n = read(sockfd, line, MAXLINE)) < 0)
{
if (errno == ECONNRESET)
{
close(sockfd);
events[i].data.fd = -1;
}
else
{
std::cout << "readline error" << std::endl;
}
}
else if (n == 0)
{
close(sockfd);
events[i].data.fd = -1;
}
ev.data.fd = sockfd;
ev.events = EPOLLOUT | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
else if (events[i].events & EPOLLOUT)
{
sockfd = events[i].data.fd;
write(sockfd, line, n);
ev.data.fd = sockfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
}
}
}
}
客户端代码:
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 5555
#define BUFSIZE 1024
int getSocket(){
int fd =socket( AF_INET, SOCK_STREAM, 0 );
if(-1 == fd){
}
return fd;
}
int main()
{
int fd = getSocket();
if (-1 == fd)
{
return -1;
}
struct sockaddr_in remote_addr; //服务器端网络地址结构体
memset(&remote_addr, 0, sizeof(remote_addr)); //数据初始化--清零
remote_addr.sin_family = AF_INET; //设置为IP通信
remote_addr.sin_addr.s_addr = inet_addr(SERVER_IP); //服务器IP地址
remote_addr.sin_port = htons(SERVER_PORT); //服务器端口号
int con_result = connect(fd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr));
if (con_result < 0)
{
return -1;
}
char buf[32] = {0};
while (1)
{
scanf("%s", buf);
int ret = send(fd, buf, strlen(buf), 0);
if (-1 == ret)
{
perror("send");
exit(1);
}
if (!strcmp(buf, "bye"))
{
break;
}
memset(buf, 0, sizeof(buf));
}
close(fd);
return 0;
}