Linux下实现I/O服用的系统调用主要有select、poll、和epoll。
select和poll的原理实现基本一致,都是在一定的指定时间内监听用户感兴趣的文件描述符上的可读可写和异常等事件,返回的是整个用户注册的事件集合,需要从整个用户表中轮询寻找就绪的文件描述符。
epoll的实现是往内核的事件表写入事件,直接返回的是触发的事件,此方法适用于连接数较多,活动连接较少的情况,当活动的连接数目较多的时候,其回调函数触发维护活动时间表的次数变多,效率反而不如select和poll。
select和poll都只能工作在LT模式,epoll可以工作在ET高效模式,并且支持EPOLLONESHOT事件。(LT:电平触发模式,当检测到其有事件发生并通知应用程序后,应用程序可以不立即处理该事件,当应用程序下次再调用epoll_wait时候,epoll_wait还会再次向应用程序通告此事件;ET:边沿触发模式,该模式下,epoll_wait通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用不再向应用程序通知该事件),ET模式在很大程度上降低同一个epoll事件被重复触发的次数,效率要比LT模式高
下述为epoll模型简单例子:
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <assert.h>
#define MAX_EVENT_NUMBER 1024 //存储的返回事件的大小
#define BUFFER_SIZE 1024
struct fds //工作线程传入的参数结构
{
int epollfd;
int sockfd;
}
int setnonblocking(int fd) //设置sock描述符为非阻塞
{
int old_option = fcntl(fd,F_GETFL);
int new_option = old_option |O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_option;
}
void addfd(int epollfd,int fd,bool oneshot) //注册fd描述符到内核事件表
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN |EPOLLET;
if(oneshot)
{
event.events |= EPOLLONESHOT;
}
epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
setnonblocking(fd);
}
void reset_oneshot(int epollfd,int fd) //恢复fd描述符的状态,在下一次事件到来时候正常触发
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN |EPOLLET | EPOLLONESHOT;
epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
}
void *work(void *arg) //sock接收线程回调函数,待补充
{
printf("now is working");
}
int main(int argc,char *argv[])
{
const char *ip = argv[1];
int port = atoi(argv[2]);
int ret = 0;
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr); //转换ASCII码的点十分制IP转为网络整数IP
address.sin_port = htons(port);
int listenfd = socket(PF_INET,SOCK_STREAM,0);
assert(listenfd>=0);
ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
assert(ret != -1);
ret = listen(listenfd,5);
assert(ret != -1);
epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
assert(epollfd != -1);
addfd(epollfd,listenfd,false);
while(1)
{
int ret = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1); //timeout=-1为阻塞
if(ret <0)
{
printf("epoll fail");
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); //为每个非监听文件描述符号都注册EPOLLONESHOT事件
}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");
}
}
}
close(listenfd);
return 0;
}
select实现的简单例子:(待更新)
参考资料:《Linux高性能服务器编程》,游双著,机械工业出版社