【Linux】多路复用之—epoll

本文深入讲解epoll的工作原理,包括epoll_create、epoll_ctl、epoll_wait等关键API的使用方法,对比LT与ET模式的特点,并提供了一个简单的epoll服务器代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、多路复用之——epoll

int epoll_create(int size);

int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);

int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);

1、epoll_create:创建一个epoll句柄,size参数可以忽略

当创建一个句柄后,会占用一个fd值,故在使用完后,要close(fd)。

2、epoll_ctl:注册要监听的事件类型

参数:(1)epfd:epoll_create的返回值;

   (2)op:EPOLL_CTL_ADD 注册新fd到epfd中

    EPOLL_CTL_MOD 修改已经注册的fd

    EPOLL_CTL_DEL               从epfd中删除一个fd

   (3)fd:要监听的fd;

   (4)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;

}

   (5) events集合:

EPOLLIN:表示对应的文件描述符可以读;

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急事件可读;

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET:将EPOLL设为边缘触发模式;

EPOLLLT:将EPOLL设为水平触发模式;

EPOLLONESHOT:只监听一次事件;

3、epoll_wait:收集在epoll监控的事件中已经发生的事件

参数:(1)epfd:创建的epoll句柄;

   (2)events:已分配好的epoll_events结构体,epoll会将发生的事件放到events中;

   (3)maxevents:events的大小;

   (4)timeout:NULL:没有timeout,一直阻塞等待知道某个事件发生;

    0:仅检测描述符集合的状态,然后立即返回;

    特定值:等待timeout时间,如果没有发生,超时返回。

4、epoll的两种工作模式:

epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读、阻塞写操作把处理多个文件描述符的任务饿死。

epoll工作在LT模式的时候,在收到多个数据的时候仍然会产生多个事件,支持阻塞和非阻塞接口,这样,内核告诉你一个文件描述符是否就绪了,然后你可以进行I/O,若你不做任何操作,内核还是会继续通知你的,错误率小。

ET与LT的区别在于,当一个新事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新事件到来时,ET模式下无法再次获取数据。但LT正好相反,只要一个事件对应的套接字缓冲区还有数据,就能够获取。

5、epoll模型的优点:

(1)使用内存映射(mmap)技术,避免用户到内存的拷贝;

(2)epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait时使得到通知;

(3)监视的文件描述符数量不受限制,它所支持的fd上限是最大可以打开文件的数目;

(4)I/O的效率不会随着fd数量的增加而下降,select、poll实现需要自己不断轮询所有fd集合,指到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能睡眠和唤醒多次交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只需要遍历就绪链表是否为空就行了。这节省了大量的CPU时间。这就是回调机制的性能提升。


代码示例:

epoll_server.c

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<fcntl.h>
#define MAX_NUM 64
int startup(int port)
{
	int sock=socket(AF_INET,SOCK_STREAM,0);
	if(sock<0){
		perror("socket");
		exit(1);
	}
	struct sockaddr_in local;
	local.sin_family=AF_INET;
	local.sin_port=htons(port);
	local.sin_addr.s_addr=htonl(INADDR_ANY);
	if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
		perror("bind");
		exit(2);
	}
	if(listen(sock,5)<0){
		perror("listen");
		exit(3);
	}
	return sock;
}
//set file descriptor for no blocking mode
void set_nonoblock(int _fd)
{
	int fl=fcntl(_fd,F_GETFL);
	if(fl<0){
		perror("fcntl");
		return;
	}
	fcntl(_fd,fl | O_NONBLOCK);
	return;
}
//recv data
int read_data(int _fd,char* buf,int len)
{
	ssize_t _size=-1;
	int total=0;
	while((_size=recv(_fd,buf+total,len-1,MSG_DONTWAIT))){
		if(_size>0){
			total+=_size;
		}else if(_size==0){//file end
			return 0;
		}else{
			return -1;
		}
	}
}
int main()
{
	short port=8080;
	int listen_sock=startup(port);
	struct sockaddr_in client;
	socklen_t len=sizeof(client);
	int epoll_fd=epoll_create(256);//create epoll handle
	int timeout=1000;
	if(epoll_fd<0){
		perror("epoll_create");
		exit(1);
	}
	struct epoll_event _ev;//save care fd
	_ev.events=EPOLLIN;
	_ev.data.fd=listen_sock;
	if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&_ev)<0){
		perror("epoll_ctl");
		goto LABLE;
	}
	struct epoll_event _ev_out[MAX_NUM];//save ready fd
	char buf[1024*5];
	memset(buf,'\0',sizeof(buf));
	int ready_num=-1;//save ready_fd num
	while(1){
		switch(ready_num=epoll_wait(epoll_fd,_ev_out,MAX_NUM,timeout)){
			case 0:
				printf("timeout\n");
				break;
			case -1:
				perror("epoll_wait");
				break;
			default:
				{
					int i=0;
					for(;i<ready_num;i++){
						int _fd=_ev_out[i].data.fd;
						//listen_sock
						if(_fd==listen_sock && (_ev_out[i].events & EPOLLIN)){
							int new_sock=accept(_fd,(struct sockaddr*)&client,&len);
							if(new_sock<0){
								perror("accept");
								continue;
							}
							printf("get a new connect...\n");
							set_nonoblock(new_sock);
							_ev.events=EPOLLIN | EPOLLET;
							_ev.data.fd=new_sock;
							if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&_ev)<0){
								perror("epoll_ctl");
								close(new_sock);
								continue;
							}
							continue;
						}
						//data_sock
						if(_ev_out[i].events & EPOLLIN){
							if(read_data(_fd,buf,sizeof(buf))==0){
								printf("client close...");
								epoll_ctl(epoll_fd,EPOLL_CTL_DEL,_fd,NULL);
							}
							printf("%s\n",buf);
						}
					}
				}
				break;
		}
	}
LABLE:
	close(epoll_fd);
	return 0;
}

Makefile:

epoll_server:epoll_server.c
	gcc -o $@ $^
.PHONY:clean
clean:
	rm -rf epoll_server


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值