【I/O多路复用】epoll系统调用

匿名 (未验证) 提交于 2019-12-02 23:49:02

版权声明:署名,允许他人基于本文进行创作,且必须基于与原先许可协议相同的许可协议分发本文 (Creative Commons

【1】epoll相关的系统调用

epoll是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大差异。
首先epoll使用一组函数来完成任务,而不是单个函数。
其次epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传人文件描述符集或事件集。
但是epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。

epoll_create()

这个文件描述符使用如下epoll create函数来创建:

#include<sys/epoll.h> int epoll_create(int size) 

其中size参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。

返回值
该函数返回一个文件描述符,用作其他所有epoll系统调用的第一个参数,用来指定要访问的内核事件表。

epoll_ctl()

#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

参数epfd是通过epoll_create创建的epoll实例
参数fd是将要被监视的文件描述符
参数op是epoll操作符
其中参数op有如下三个有效值如下

  • EPOLL_CTL_ADD ,向epfd注册fd的上的event
  • EPOLL_CTL_MOD,修改fd已注册的event
  • EPOLL_CTL_DEL,从epfd上删除fd的event

参数event指定事件,他是一个epoll_event结构指针类型如下:

struct epoll_event  { 	__uint32_t   events;	//	epoll事件  	epoll_data_t data;		//	用户数据  }; 

其中events成员描述事件类型,epoll支持的事件类型和poll基本相同,唯一的区别就是在poll的事件类型前加上E代表epoll,如下:

事件 描述
EPOLLIN 有数据可读
EPOLLRDNORM 有普通数据可读
EPOLLRDBAND 有优先数据可读
EPOLLPRI 有紧迫数据可读
EPOLLOUT 写数据不会导致阻塞
EPOLLWRNORM 写普通数据不会导致阻塞
EPOLLWRBAND 写优先数据不会导致阻塞
EPOLLMSGSIGPOLL 消息可用

data成员用于存储用户数据,其类型为epoll_data_t定义如下:

typedef union epoll_data  { 	void  *ptr; 	int  fd; 	__uint32_t  u32; 	__uint64_t  u64; } epoll_data_t; 

epoll_data_t是一个联合体.
fd是我们最常用的成员,他指定事件所从属的目标文件描述符
ptr可指定与fd相关的用户数据。

返回值
epoll_ctl()执行成功时返回0,失败时返回-1,并置errno

epoll_wait()

//返回就绪事件描述符的个数 #include<sys/epoll.h> int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout); 

epoll_wait()用于等待事件的就绪,类似于select()的调用。
参数events用来从内核得到事件的集合。
参数maxevents告诉内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size。
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)

返回值
该函数调用成功时返回需要处理的事件个数,如返回0表示已超时。

【2】代码示例

使用epoll实现多客户端服务器的交互
server.c

#include <stdio.h> #include <string.h> #include <assert.h> #include <stdlib.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h>  #define MAXFD 10  //向内核事件表epfd 中添加 新事件的文件描述符fd void epoll_add(int epfd, int fd) { 	// 设置epoll_event的结构成员  	struct epoll_event ev; 	ev.events = EPOLLIN; 	ev.data.fd = fd;  	//EPOLL_CTL_ADD添加新事件及描述符到内核事件表 	if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1) 	{ 		perror("epoll_ctl add error\n"); 	} }  void epoll_del(int epfd, int fd) { 	//从内核事件表中移除fd 	if (epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1) 	{ 		perror("epoll_ctl del error\n"); 	} }  int create_sockfd();  int main() { 	int sockfd = create_sockfd(); 	assert(sockfd != -1);  	//创建内核事件表  	int epfd = epoll_create(MAXFD); 	assert(epfd != -1);  	// 设置epoll_event的结构成员 	struct epoll_event ev; 	ev.data.fd = sockfd; 	ev.events = EPOLLIN; 	epoll_add(epfd, sockfd);  	// 定义events数组存放就绪描述符 	struct epoll_event events[MAXFD];  	while (1) 	{    		/*  		epoll_wait返回的是前n个已经全就绪的文件描述符, 		那么我们不用全部遍历,只遍历前n个就可以 		超时时间设置为5秒 		*/ 		int n = epoll_wait(epfd, events, MAXFD, 5000);  		//  epoll_wait() 调用失败 		if (n == -1) 		{ 			perror("epoll wait error!\n"); 		} 		else if (n == 0) 		{ 			printf("time out!\n"); 		}  		// 只遍历前n个,因为内核已告诉我们前n个有就绪事件 		else 		{  			int i = 0; 			for (; i < n; ++i) 			{ 				int fd = events[i].data.fd; 				if (fd == -1) 				{ 					continue; 				}  				// events 为内核为我们返回的就绪事件  				if (events[i].events & EPOLLIN) 				{ 					if (fd == sockfd) 					{ 						struct sockaddr_in caddr; 						int len = sizeof(caddr); 						 						// 接收一个套接字已建立的连接,得到连接套接字connfd  						int connfd = accept(sockfd, (struct sockaddr*) & caddr, (socklen_t *)&len);  						if (connfd < 0) 						{ 							continue; 						} 						printf("accept c=%d\n", connfd);  						// 将连接套接字connfd,添加到内核事件表中 						epoll_add(epfd, connfd); 					} 					else 					{ 						char buff[128] = { 0 };  						// recv用来接收客端数据  						int res = recv(fd, buff, 127, 0); 						if (res <= 0) 						{ 							//不能先close,应该先调用epoll_del() 							//因为先调用close关闭了文件描述符后,再调用epoll_del() 							//内核就找不到所要从内核事件表中移除的文件描述符 							 							epoll_del(epfd, fd); 							close(fd); 							printf("one client over\n"); 							continue; 						} 						printf("buff %d = %s\n", fd, buff); 						send(fd, "OK", 2, 0); 					} 				} 			} 		} 	} }  int create_sockfd() { 	//创建监听套接字(socket描述符),指定协议族ipv4,字节流服务传输 	int sockfd = socket(AF_INET, SOCK_STREAM, 0); 	if (sockfd == -1) 	{ 		return -1; 	}  	// socket专用地址信息设置  	struct sockaddr_in saddr; 	memset(&saddr, 0, sizeof(saddr));  	saddr.sin_family = AF_INET; 	saddr.sin_port = htons(6000); 	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  	//命名套接字,将socket专用地址绑定到socket描述符上 	int res = bind(sockfd, (struct sockaddr*) & saddr, sizeof(saddr)); 	if (res == -1) 	{ 		return -1; 	} 	// 创建监听队列 	listen(sockfd, 5); 	return sockfd; }   

client.c

#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h>  int main() { 	int sockfd = socket(AF_INET,SOCK_STREAM,0); 	assert(sockfd != -1 );  	struct sockaddr_in saddr; 	memset(&saddr,0,sizeof(saddr)); 	saddr.sin_family = AF_INET; 	saddr.sin_port = htons(6000); 	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  	int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//向服务器发起链接 	assert(res != -1);  	while(1) 	{ 		char buff[128] = {0}; 		printf("Please Input:"); 		fgets(buff,128,stdin); 		if(strncmp(buff,"end",3) ==0 ) 		{ 			break; 		} 		send(sockfd,buff,strlen(buff),0); 		memset(buff,0,128); 		recv(sockfd,buff,127,0); 		printf("RecvBuff:%s\n",buff);         printf("\n"); 	} 	close(sockfd); }  

文章来源: https://blog.csdn.net/weixin_39411321/article/details/96457788
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!