通常,网络程序在下列情况下需要使用I/O多路复用技术。
TCP服务器要同时处理监听socket和连接socket。这是I/O复用使用最多的场合
。int select(
int nfds,
fd_set *readfds,
fd_set *writefds,
fd_set *exceptfds,
struct timeval *timeout
);
select后会将其重置
。一直阻塞
,直到某个文件描述符就绪。struct timeval{
long tv_sec; // 秒
long tv_usec; // 微秒
};
void FD_ZERO(fd_set *fdset);
void FD_CLR(int fd, fd_set *fdset);
void FD_SET(int fd, fd_set *fd_set);
int FD_ISSET(int fd, fd_set *fdset);
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
int errno;
int main(void){
int server_sockfd,client_sockfd;
int server_len,client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int result;
//两个文件描述符集合
fd_set readfds,testfds;//readfds用于检测输出是否就绪的文件描述符集合
server_sockfd = socket(AF_INET,SOCK_STREAM,0); // 建立服务端socket
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9000);
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr*)&server_address,server_len);
listen(server_sockfd,5);// 监听队列最多容纳5个
FD_ZERO(&readfds);// 清空置0
FD_SET(server_sockfd,&readfds);// 将服务端socket加入到集合中
/*
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
*/
while(1){
char ch;
int fd;
int nread;
testfds = readfds;//相当于备份一份,因为调用select后,传进去的文件描述符集合会被修改。
struct timeval my_time;
my_time.tv_sec = 2;
my_time.tv_usec = 0;
printf("server waiting\n");
// 监视server_sockfd与client_sockfd
//result = select(FD_SETSIZE,&testfds,(fd_set*)0,(fd_set*)0,(struct timeval* )0); //无限期阻塞,并测试文件描述符变动
result = select(FD_SETSIZE,&testfds,(fd_set*)0,(fd_set*)0,&my_time); //根据my_time中设置的时间进行等待,超过继续往下执行。
if(result < 0){//有错误发生
perror("select errno");
exit(1);
}else if(result == 0){//超过等待时间,未响应
FD_ZERO(&readfds);// 清空置0
FD_SET(server_sockfd,&readfds);// 将服务端socket重新加入到集合中
printf("no connect request \n");
continue;//没有响应的就别下去遍历了
}
//扫描所有的文件描述符(遍历所有的文件句柄),是件很耗时的事情,严重拉低效率。
for(fd = 0;fd<FD_SETSIZE;fd++){
//找到相关文件描述符,判断是否在testfds这个文件描述符集合中。
if(FD_ISSET(fd,&testfds)){
//判断是否为服务器套接字,是则表示为客户端请求连接
if(fd == server_sockfd){
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address,&client_len);
FD_SET(client_sockfd,&readfds);//将客户端socket加入到集合中,用来监听是否有数据来。
printf("adding client on fd %d\n",client_sockfd);;
}else{// 客户端来消息了
//获取接收缓存区中的字节数
ioctl(fd,FIONREAD,&nread);//即获取fd来了多少数据
//客户端数据请求完毕,关闭套接字,并从集合中清除相应的套接字描述符
if(nread ==0){
close(fd);
FD_CLR(fd,&readfds);//去掉关闭的fd
printf("removing client on fd %d\n", fd);
}else{//处理客户数请求
read(fd,&ch,1);
sleep(5);
printf("serving client on fd %d\n", fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
return 0;
}
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/time.h>
int main(){
int client_sockfd;
int len;
struct sockaddr_in address;//服务器端网络地址结构体
int result;
char ch = 'A';
client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(9000);
len = sizeof(address);
result = connect(client_sockfd, (struct sockaddr*)&address, len);
if (result == -1){
perror("oops: client2");
exit(1);
}
//第一次读写
write(client_sockfd, &ch, 1);
read(client_sockfd, &ch, 1);
printf("the first time: char from server = %c\n", ch);
sleep(5);
//第二次读写
write(client_sockfd, &ch, 1);
read(client_sockfd, &ch, 1);
printf("the second time: char from server = %c\n", ch);
close(client_sockfd);
return 0;
}
int poll(
struct pollfd *fds,
nfds_t nfds,
int timeout
);
struct pollfd {
int fd;
short events;
short revents;
};
文件描述符
。注册的事件
。如下图所示。内核填充
。#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>
#define MAX_FD 8192 //最大文件标识符
struct pollfd fds[MAX_FD];
int cur_max_fd = 0; //当前要监听的最大文件描述符+1,减少要遍历的数量。
int main(void){
int server_sockfd,client_sockfd;
int server_len,client_len;
struct sockaddr_in server_address,client_address;
int result;
server_sockfd = socket(AF_INET,SOCK_STREAM,0);//服务端socket
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(9000);
server_len = sizeof(server_address);
bind(server_sockfd,(struct sockaddr*)&server_address,server_len);
listen(server_sockfd,5);
//添加待监测文件描述符到fds数组中
fds[server_sockfd].fd = server_sockfd;
fds[server_sockfd].events = POLLIN;
fds[server_sockfd].revents = 0;
if(cur_max_fd <= server_sockfd){
cur_max_fd = server_sockfd+1;
}
while(1){
char ch;
int i,fd;
int nread;
printf("server waiting\n");
result = poll(fds,cur_max_fd,1000);
if(result <0){
perror("server5");
exit(1);
}else if(result == 0){
printf("no connect,end waiting\n");
}else{//大于0,返回的是fds中处于就绪态的文件描述符个数。
}
//扫描文件描述符
for(i = 0; i < cur_max_fd;i++){
if(fds[i].revents){//有没有结果,没有结果说明该文件描述符上还未发生事件。
fd= fds[i].fd;
//判断是否为服务器套接字,是则表示为客户端请求连接
if(fd == server_sockfd){
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address,&client_len);
fds[client_sockfd].fd = client_sockfd;
fds[client_sockfd].events = POLLIN;
fds[client_sockfd].revents = 0;
if(cur_max_fd <= client_sockfd){
cur_max_fd = client_sockfd + 1;
}
printf("adding client on fd %d\n",client_sockfd);
}else{//客户端socket中有数据请求
if(fds[i].revents & POLLIN){//读
nread = read(fd,&ch,1);
if(nread == 0){
close(fd);
memset(&fds[i],0,sizeof(struct pollfd));
printf("removing client on fd %d\n",fd);
}else{//写
sleep(5);
printf("serving client on fd %d,receive: %c\n",fd,ch);
ch++;
fds[i].events = POLLOUT;//添加一个写事件监听
}
}else if(fds[i].revents & POLLOUT){//写
write(fd,&ch,1);
fds[i].events = POLLIN;
}
}
}
}
}
return 0;
}
Linux特有的I/O复用函数
。它在实现和使用上与select和poll有很大的差异。一组函数
来完成任务,而不是单个函数
。事件表
中,不需要像select与poll那样每次都要重复传入文件描述符集合或是事件集。epoll需要使用一个额外的文件描述符,在内核中唯一标识这个事件表
。int epoll_create(int size);
返回创建的epoll实例文件描述符,在其它epoll相关函数中指定要访问的内核事件表
。int epoll_ctl(
int epfd,
int op,
int fd,
struct epoll_event *event
);
struct epoll_event{
uint32_t events;
epoll_data_t data;
};
typedef union epoll_data{
void *ptr; // 用户自定义使用
int fd; // 指定事件文件描述符
uint32_t u32;
uint64_t u64;
}epoll_data_t;
当我们调用epoll_wait后,evlist数组中的epoll_event每个data参数为我们在一开始(即调用epoll_ctl)所指定的内容,比如像上面所说的我们指定了自定义数据ptr,最终某一fd产生了我们监视的事件,我们可以在其对应的epoll_event的data中取到。
例如下方epoll-简易web服务器中的_ConnectStat结构体。int epoll_wait(int epfd,
struct epoll_event * evlist,
int maxevents,
int timeout
);
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<assert.h>
#include<fcntl.h>
#include<unistd.h>
//因为下面的函数指针所以单独拿出来声明typedef
typedef struct _ConnectStat ConnectStat;
typedef void(*response_handler) (ConnectStat * stat);
// 保存自定义数据的结构体,调用epoll时用epoll_data_t中的ptr存储
struct _ConnectStat {
int fd; //文件描述符
char name[64]; //姓名
char age[64]; //年龄
struct epoll_event _ev; //当前文件句柄对应epoll事件
int status; //0-未登录,1-已登录
response_handler handler; //不同页面的处理函数
};
// 初始化一个自定义数据存储结构体
ConnectStat * stat_init(int fd);
// 将新链接进来的客户端fd放入当前epoll所对应的内核事件表中
void connect_handle(int new_fd);
// 请求响应-指定对应的处理函数
void do_http_respone(ConnectStat * stat);
// 处理http请求
void do_http_request(ConnectStat * stat);
// 响应处理函数——请求链接返回的内容
void welcome_response_handler(ConnectStat * stat);
// 响应处理函数——commit后返回的内容
void commit_respone_handler(ConnectStat * stat);
// 将新链接进来的客户端fd放入当前epoll所对应的内核事件表中
void connect_handle(int new_fd);
// 创建一个监听套接字 - 略
int startup(char* _ip, int _port);
// 将fd-设置为非阻塞状态,即给指定fd添加状态
void set_nonblock(int fd);
// 打印信息提示ip:port
void usage(const char* argv);
// 响应头
const char *main_header = "HTTP/1.0 200 OK\r\nServer: Xuanxuan Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
static int epfd = 0;// epoll文件描述符,对应一张内核事件表
// 初始化自定义数据存储结构体
ConnectStat * stat_init(int fd) {
ConnectStat * temp = NULL;
temp = (ConnectStat *)malloc(sizeof(ConnectStat));
if (!temp) {
fprintf(stderr, "malloc failed. reason: %m\n");
return NULL;
}
memset(temp, '\0', sizeof(ConnectStat));
temp->fd = fd;
temp->status = 0;
}
// 解析http请求
void do_http_request(ConnectStat * stat) {
//读取和解析http 请求
char buf[4096];
char * pos = NULL;
ssize_t _s = read(stat->fd, buf, sizeof(buf) - 1);
if (_s > 0){// 读取到数据
buf[_s] = '\0';
// printf("receive from client:%s\n", buf);//GET / HTTP/1.1
pos = buf;
//Demo 仅仅演示效果,不做详细的协议解析
if (!strncasecmp(pos, "GET", 3)) {// 是否为Get请求
stat->handler = welcome_response_handler;// 设置执行函数
}else if (!strncasecmp(pos, "Post", 4)) {// 是否为POST请求
//获取 uri
//printf("---Post----\n");
pos += strlen("Post");
while (*pos == ' ' || *pos == '/') ++pos;
// POST /commit HTTP/1.1
if (!strncasecmp(pos, "commit", 6)) {//提交
int len = 0;
//printf("post commit --------\n");
pos = strstr(buf, "\r\n\r\n");//返回第一次出现\r\n\r\n的位置
char *end = NULL;
// 拿到姓名与年龄
if (end = strstr(pos, "name=")) {
pos = end + strlen("name=");
end = pos;
while (('a' <= *end && *end <= 'z') || ('A' <= *end && *end <= 'Z') || ('0' <= *end && *end <= '9')) end++;
len = end - pos;
if (len > 0) {// 将姓名存入自定义结构体中
memcpy(stat->name, pos, end - pos);
stat->name[len] = '\0';
}
}
if (end = strstr(pos, "age=")) {
pos = end + strlen("age=");
end = pos;
while ('0' <= *end && *end <= '9') end++;
len = end - pos;
if (len > 0) {// 将年龄存入自定义结构体中
memcpy(stat->age, pos, end - pos);
stat->age[len] = '\0';
}
}
stat->handler = commit_respone_handler;// 设置响应函数
}
else {
stat->handler = welcome_response_handler;// 设置响应函数
}
}
else {
stat->handler = welcome_response_handler;// 设置响应函数
}
stat->_ev.events = EPOLLOUT; // 修改事件类型
epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev); //修改,交给eoill监视。
}else if (_s == 0){// 没有读取到数据,客户端关闭。
printf("client: %d close\n", stat->fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, stat->fd, NULL);// 将对应fd从对应epoll的内核事件表中删除
close(stat->fd);// 关闭套接字
free(stat); // 释放内存
}else{// read发生错误
perror("read");
}
}
void do_http_respone(ConnectStat * stat) {
stat->handler(stat);// 调用对应设置的函数
}
void welcome_response_handler(ConnectStat * stat) {
const char * welcome_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>Hello World</h2><br/><br/>\n\
<form action=\"commit\" method=\"post\">\n\
姓名: <input type=\"text\" name=\"name\" />\n\
<br/>年龄: <input type=\"password\" name=\"age\" />\n\
<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\
<input type=\"reset\" value=\"重置\" />\n\
</form>\n\
</div>\n\
</body>\n\
</html>";
char sendbuffer[4096];
char content_len[64];
strcpy(sendbuffer, main_header);// 拷贝响应头
snprintf(content_len, 64, "Content-Length: %d\r\n\r\n", (int)strlen(welcome_content));
strcat(sendbuffer, content_len);
strcat(sendbuffer, welcome_content);
//printf("send reply to client \n%s", sendbuffer);
// 写给客户端-即发起请求的浏览器
write(stat->fd, sendbuffer, strlen(sendbuffer));
stat->_ev.events = EPOLLIN; // 修改关心的事件
//stat->_ev.data.ptr = stat;
epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev);
}
void commit_respone_handler(ConnectStat * stat) {
const char * commit_content = "\
<html lang=\"zh-CN\">\n\
<head>\n\
<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\
<title>This is a test</title>\n\
</head>\n\
<body>\n\
<div align=center height=\"500px\" >\n\
<br/><br/><br/>\n\
<h2>欢迎 %s ,年龄 %s!</h2><br/><br/>\n\
</div>\n\
</body>\n\
</html>\n";
char sendbuffer[4096];
char content[4096];
char content_len[64];
int len = 0;
len = snprintf(content, 4096, commit_content, stat->name, stat->age);
strcpy(sendbuffer, main_header); //响应头
snprintf(content_len, 64, "Content-Length: %d\r\n\r\n", len);
strcat(sendbuffer, content_len);
strcat(sendbuffer, content);
//printf("send reply to client \n%s", sendbuffer);
write(stat->fd, sendbuffer, strlen(sendbuffer));
stat->_ev.events = EPOLLIN; // 修改关心的事件
epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev);// 交给epoll来监视
}
void usage(const char* argv){
printf("%s:[ip][port]\n", argv);
}
void set_nonblock(int fd){
// 这里的文件状态标志flag即open函数的第二个参数
int fl = fcntl(fd, F_GETFL);// 获取设置的flag
fcntl(fd, F_SETFL, fl | O_NONBLOCK);// 设置flag
// fcntl函数 https://blog.csdn.net/zhoulaowu/article/details/14057799
// O_NONBLOCK https://blog.csdn.net/cjfeii/article/details/115484558
}
int startup(char* _ip, int _port){
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0){
perror("sock");
exit(2);
}
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in local;
local.sin_port = htons(_port);
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(_ip);
if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){
perror("bind");
exit(3);
}
if (listen(sock, 5) < 0){
perror("listen");
exit(4);
}
return sock; //返回套接字
}
#include "epoll_server.h"
int main(int argc, char *argv[]){
if (argc != 3){//检查输入的参数个数是否正确
usage(argv[0]);
exit(1);
}
//创建一个server socket
int listen_sock = startup(argv[1], atoi(argv[2]));
//创建epoll
epfd = epoll_create(256);
if (epfd < 0){//创建失败
perror("epoll_create");
exit(5);
}
ConnectStat * stat = stat_init(listen_sock);// 自定义数据存储
struct epoll_event _ev; //epoll事件结构体
_ev.events = EPOLLIN; //设置关心事件为读事件
_ev.data.ptr = stat; //接收返回值
//将listen_sock添加到epfd中,关心读事件,有客户端来请求链接
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &_ev);
struct epoll_event revs[64];//接收返回的产生响应的事件
int timeout = -1;// -1无限期阻塞
int num = 0;// 就绪的请求I/O个数
while (1){
//检测事件
switch ((num = epoll_wait(epfd, revs, 64, timeout))){
case 0: //监听超时
printf("timeout\n");
break;
case -1: //出错
perror("epoll_wait");
break;
default:{ //>0,即返回了需要处理事件的数目
//拿到对应的文件描述符
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
for (int i = 0; i < num; i++){//
//拿到该fd相关的链接信息
ConnectStat * stat = (ConnectStat *)revs[i].data.ptr;
int rsock = stat->fd;//拿到对应的fd,进行如下的判断
if (rsock == listen_sock && (revs[i].events) && EPOLLIN) {// 有客户端链接
int new_fd = accept(listen_sock, (struct sockaddr*)&peer, &len);
if (new_fd > 0){//accept成功
printf("get a new client:%s:%d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));
connect_handle(new_fd);// 监听新进来的客户端fd
}
}else {//除server socket 之外的其他fd就绪
if (revs[i].events & EPOLLIN){//有数据可读
do_http_request((ConnectStat *)revs[i].data.ptr);
}else if (revs[i].events & EPOLLOUT){//写
do_http_respone((ConnectStat *)revs[i].data.ptr);// 完成响应后会再次关心EPOLLIN事件,等待下一次请求。
}else{
}
}
}
}
break;
}
}
return 0;
}
如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。
这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符,很大程度上降低了同一个epoll事件被重复触发的次数。
同时,应用程序应立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件(之后的读写事件就会通知,只是这次的不会了)。
保证一个socket连接在任一时刻都只被一个线程处理,从而保证连接的完整性,避免了很多可能的竞态条件。
注册完EPOLLONESHOT事件的socket一旦被某个线程处理完毕,应立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下次可读时,其EPOLLIN事件能被触发,同时也给其它线程处理这个socket的机会。
用于监听链接请求的Server_socket是不能注册EPOLLONESHOT事件的,否则应用程序只能处理一个客户链接,因为后续的客户链接请求将不再触发Server_socket上的EPOLLIN事件。
如果某一线程处理完成该socket上的请求之后,又在该socket上收到了新的客户请求,该线程将继续接触这个socket。
while( 1 ){
int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
if ( ret < 0 )break;
for ( int i = 0; i < ret; i++ ){
int sockfd = events[i].data.fd;
if ( sockfd == listenfd ){// 有链接请求接入
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof( client_address );
int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
addfd( epollfd, connfd, true );
}
else if ( events[i].events & EPOLLIN ){
pthread_t thread;
fds fds_for_new_worker;
fds_for_new_worker.epollfd = epollfd;
fds_for_new_worker.sockfd = sockfd;
// 创建一个线程去处理
pthread_create( &thread, NULL, worker, ( void* )&fds_for_new_worker );
}
else printf( "something else happened \n" );
}
}
void addfd( int epollfd, int fd, bool oneshot ){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
if( oneshot ){
event.events |= EPOLLONESHOT;// 注册EPOLLONESHOT事件
}
epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
setnonblocking( fd );// 设置为非阻塞fd
}
int setnonblocking( int fd ){
int old_option = fcntl( fd, F_GETFL );// 拿到之前对该fd的设置属性
int new_option = old_option | O_NONBLOCK;// 追加O_NONBLOCK属性
fcntl( fd, F_SETFL, new_option );// 设置
return old_option;// 当前示例Demo返回无意义,未使用。
}
void* worker( void* arg ){
int sockfd = ( (fds*)arg )->sockfd;
int epollfd = ( (fds*)arg )->epollfd;
printf( "start new thread to receive data on fd: %d\n", sockfd );
char buf[ BUFFER_SIZE ];
memset( buf, '\0', BUFFER_SIZE );
while( 1 ){// 因为是非阻塞的,所以要一次性读光,即要立即处理,因为epoll_wait只会提醒一次。
int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
if( ret == 0 ){
close( sockfd );
printf( "foreiner closed the connection\n" );
break;
}
else if( ret < 0 ){
if( errno == EAGAIN ){// 读光啦
reset_oneshot( epollfd, sockfd );// 重置注册事件
printf( "read later\n" );
break;
}
}
else{
printf( "get content: %s\n", buf );
// sleep 5秒,模拟数据处理过程
sleep( 5 );
}
}
printf( "end thread receiving data on fd: %d\n", sockfd );
}
void reset_oneshot( int epollfd, int fd ){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );
}
在线修改
,导致应用程序下次再调用select前不得不重置
这三个fd_set集合。同时我们也需要在使用前进行备份
。select与poll每次调用后,都需要遍历整个用户关心的事件集合,无论其中的事件是否就绪,所以应用程序检索就绪文件描述符的时间复杂度为O(n)。
事件表
,并提供独立的系统调用epoll_ctl来往其中进行添加、删除、修改事件,而无须
反复地从用户空间读入这些事件。epoll_wait系统调用的events参数负责保存这些就绪的事件,使得应用程序检索就绪文件描述符的时间复杂度达到O(1)。
不可预期
的后果。轮询
的方式,每次扫描整个注册文件描述符集合
,将就绪的文件描述符返回给用户程序。检测就绪事件的时间复杂度为O(n)。回调
的方式,内核检测到就绪的文件描述符时,将触发回调函数,回调函数将该文件描述符上对应的事件插入内核就绪事件队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。
活动连接比较多
的时候,epoll_wait的效率未必
比select和poll高,因为此时回调函数被触发得过于频繁。所以epoll_wait适用于连接数量多,但活动连接较少的情况。