高级I/O之多路转接epoll
程序员文章站
2022-06-14 14:38:18
...
一.关于epoll函数
1.什么是epoll
是为了处理大量的句柄而作了改进的poll。被公认为linux2.6下性能最好的多路I/O就绪通知方式。
2.epoll函数
epoll函数与select函数其中一个不同点是epoll分别用三个函数来实现多路转接的功能,而select函数用一个函数实现。
-
epoll_creat()
作用:创建一个epoll模型,返回的是epoll模型句柄
参数:size参数在linux2.8.6之后可以被忽略,此处的size不建议写太大。
注意:当创建好epoll句柄时,它会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则会导致fd()耗尽。- epoll_ctl()
作用:是向epoll模型中增加删除或者修改对应文件描述符中的对应事件
参数:
- epoll_ctl()
- 第一个参数是epoll_creat()的返回值;
- 第二个参数表示动作,可以用三个宏来表示:EPOLL_CTL _ ADD:注册新的fd到epfd中去;EPOLL _ CTL _MOD:修改已经注册的fd的监听事件;EPOLL _CTL _DEL:从epfd中删除一个fd;
- 第三个参数是需要监听的fd。
- 第四个参数告诉内核需要监听什么事,struct epoll_event结构如下:
events可以是以下几个宏的集合:
二.epoll工作原理
epoll_creat的实质是在创建红黑树,因为遍历红黑树要比select中的数组高效的多。
epoll_wait只负责在就绪队列中拿节点时间复杂度为O(1),若队列为空,则表示无就绪队列,若不为空,则放入的事件是按顺序的。
epoll_ctl是向红黑树中添加节点(即添加文件描述符及文件描述符上的事件),当我们注册完文件描述符及其事件,则操作系统会采用回掉的机制来通知我们哪些文件描述符上的哪些事件就绪,将就绪的事件放入队列中去。epoll_ctl的实质是在修改内核态的红黑树
三.实现epoll服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/select.h>
#include<string.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/epoll.h>
#include<unistd.h>
#define M 56
static void Usage(const char* proc)
{
printf("usage:%s [local_ip] [local_port]\n",proc);
}
int startup(char* ip,int port )
{
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
exit(3);
}
if(listen(sock,10)<0){
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
return 0;
}
int listen_sock = startup(argv[1],atoi(argv[2]));
int ep_fd = epoll_create(256);
if(ep_fd<0){
perror("epoll_create");
exit(5);
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if(epoll_ctl(ep_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0)
{
perror("epoll_ctl");
exit(6);
}
int timeout = 10000;
int nums = -1;//就绪事件的个数
int maxevent = M;
struct epoll_event array[M];
while(1){
nums = epoll_wait(ep_fd,array,maxevent,timeout);
switch(nums){
case -1:
perror("epoll_wait");//epoll_wait出错
break;
case 0:
printf("timeout...\n");//超时
break;
default:
{
int i=0;
for(;i<nums;++i)
{//遍历就绪事件
if(array[i].data.fd==listen_sock && array[i].events & EPOLLIN){//listen_sock的读事件
struct sockaddr_in client;
socklen_t len = sizeof(client);
//创建new_sock
int new_sock = accept(listen_sock,\
(struct sockaddr*)&client,&len);
if(new_sock<0){
perror("accept");
exit(7);
}
//建立客户端的连接
printf("get a new client:[%s:%d]\n",inet_ntoa(client.sin_addr),\
ntohs(client.sin_port));
//listen_sock的读事件完成后,此时客户端可以向客户端写数据,将关心的写时间加入句柄中
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = new_sock;
epoll_ctl(ep_fd,EPOLL_CTL_ADD,new_sock,&event);
}else if(array[i].data.fd != listen_sock){//普通事件
if(array[i].events & EPOLLIN)//读就绪
{//read ready
int buf[1024];
ssize_t s = read(array[i].data.fd,buf,sizeof(buf)-1);//BUG!!!
if(s<0){
perror("read");
close(array[i].data.fd);
epoll_ctl(ep_fd,EPOLL_CTL_DEL,array[i].data.fd,0);
exit(8);
}else if(s==0){
printf("clilent is quit\n");
close(array[i].data.fd);
epoll_ctl(ep_fd,EPOLL_CTL_DEL,array[i].data.fd,0);
}else{
buf[s] = 0;
printf("client#:%s\n",buf);
array[i].events = EPOLLOUT;
//读 取成功,将该时间再改为写
epoll_ctl(ep_fd,EPOLL_CTL_MOD,\
array[i].data.fd,&array[i]);
}
}else if(array[i].events & EPOLLOUT){//write ready写事件就绪
printf("hello\n");
const char* msg = "HTTP/1.0 OK 200\r\n\r\n\
<html><h1>hello world</h1></html>" ;//在页面显示hello world
array[i].events = EPOLLIN;
write(array[i].data.fd,msg,strlen(msg));
close(array[i].data.fd);
epoll_ctl(ep_fd,EPOLL_CTL_MOD,\
array[i].data.fd,&array[i]);
}
}else{//other ready
}
}
}
}
}
return 0;
}
上一篇: 课时6_Python之常用操作符
下一篇: linux服务器基本安全配置手册