欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Unix C学习之IO多路复用之epoll

程序员文章站 2022-06-14 11:02:17
...

(一)epoll, select, poll区别

图片来自select、poll和epoll的总结对比
文章参考select、poll、epoll之间的区别(搜狗面试)
Unix C学习之IO多路复用之epoll

  1. select它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。

  2. poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.

  3. epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

  4. select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

(二)epoll代码

用epoll实现服务器
t_net.c工具函数,server.c用到了,与epoll无关

#include <t_net.h>

int s_bind(int type, uint16_t port){
	SA4 serv;//具体的ipv4地址结构
	//创建socket设备,返回该设备的文件描述符
	int sfd = socket(AF_INET, type, 0);
	if(-1 == sfd) E_MSG("socket", -1);
	//初始化serv成员
	serv.sin_family = AF_INET;
	serv.sin_port = htons(port);
	serv.sin_addr.s_addr = htons(INADDR_ANY);
	//将本地地址(ip地址和端口)绑定到socket设备sfd
	int b = bind(sfd, (SA *)&serv, sizeof(serv));
	if(-1 == b) E_MSG("bind", -1);
	//将sfd设置为被动连接状态。监听客户端连接的请求,将客户端到来的连接请求放入到未决连接队列中
	return sfd;
}

int s_listen(int type, uint16_t port, int backlog){
	int fd = s_bind(type, port);
	if(-1 == fd) return -1;
	int lis = listen(fd, backlog);
	if(-1 == lis) E_MSG("listen", -1);
	return fd;
}

//不保存客户端IP地址和端口号
int n_accept(int fd){
	int cfd = accept(fd, NULL, NULL);
	if(-1 == cfd) E_MSG("accept", -1);
	return cfd;
}

//输出客户端IP地址和端口号
int h_accept(int fd){
	SA4 cli;
	char IP[32];
	socklen_t len = sizeof(SA4);
	int cfd = accept(fd, (SA *)&cli, &len);
	if(-1 == cfd) E_MSG("accept", -1);
	printf("ip:%s\n", inet_ntop(AF_INET, &cli.sin_addr, IP, 32));
	return cfd;
}

 
server.c服务器代码,epoll具体使用流程

#include <unistd.h>
#include <t_net.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>
#include <ctype.h>
#include <sys/epoll.h>

int events_handle(int, struct epoll_event *);

int main(void){
	//创建一个epoll实例
	int epfd = epoll_create(4);
	if(-1 == epfd) E_MSG("epoll_create", -1);
	//创建socket设备,返回该设备的文件描述符
	//绑定到4466端口
	int sfd = s_listen(SOCK_STREAM, 4466, 5);
	if(-1 == sfd) return -1;
	//将sfd添加到epoll中
	//sfd的哪个事件需要代理
	struct epoll_event ev;
	ev.events = EPOLLIN;
	ev.data.fd = sfd;
	int v = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);
	if(-1 == v) E_MSG("epoll_ctl", -1);
	struct epoll_event events[100];
	printf("accept...\n");
	while(1){
		//EPOLL等待事件的发生
		int maxfd = epoll_wait(epfd, events, 100, -1);
		if(-1 == maxfd) E_MSG("epoll_wait", -1);
		//有监测的事件的发生,maxfd代表有多少个事件
		for(int i=0; i<maxfd; i++){
			if(events[i].data.fd == sfd){
				//说明未决连接队列非空
				int cfd = h_accept(sfd);
				if(-1 == cfd) return -1;
				//将连接描述符添加到epoll
				ev.events = EPOLLIN;
				ev.data.fd = cfd;
				v = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
				if(-1 == v) E_MSG("epoll_ctl", -1);
			}else{//有客户端请求i/o的到来		
				events_handle(epfd, &events[i]);
			}
		}
	}
	return 0;
}


int events_handle(int epfd, struct epoll_event *ev){
	char buf[1024];
	int fd = ev->data.fd;
	int r = read(fd, buf, 1024);
	//处理客户端的信息
	for(int i=0; i<r; i++)
		buf[i] = toupper(buf[i]);
	write(fd, buf, r);
	if(!strcmp(buf, "BYEBYE")){
		epoll_ctl(epfd, EPOLL_CTL_DEL, fd, ev);
		close(fd);
	}
	return 0;
}

 
client.c客户端代码

#include <unistd.h>
#include <string.h>
#include <t_net.h>

int main(int argc, char *argv[]){
	char buf[128];
	char msg[128];
	SA4 serv;
	//创建一个通讯端点,返回该设备的文件描述符
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if(-1 == fd) E_MSG("socket", -1);
	//初始化serv的成员
	serv.sin_family = AF_INET;
	serv.sin_port = htons(4466);
	//127.0.0.1 text--->binary
	inet_pton(AF_INET, argv[1], &serv.sin_addr);

	//使用fd设备向服务器发起连接
	int c = connect(fd, (SA *)&serv, sizeof(serv));
	if(-1 == c) E_MSG("connect", -1);
	//到这里,已经和服务器建立了连接
	//向服务器发送请求信息
	while(1){
		char *tmp = fgets(msg, sizeof(msg), stdin);
		if(strlen(tmp) == sizeof(msg)-1 && tmp[sizeof(msg)-2] != '\n'){
			scanf("%*[^\n]");
			scanf("%*c");
		}
		tmp[strlen(msg)-1] = '\0';
		int count = write(fd, msg, strlen(msg)+1);
		if(-1 == count) E_MSG("write", -1);
		//阻塞等待服务器的响应消息
		//将响应结果存放到buf
		int r = read(fd, buf, 128);
		//处理响应结果,将得到的字符串输出到屏幕
		if(!strcmp(buf, "BYEBYE")) break;
		count = write(1, buf, r);
		if(-1 == count) E_MSG("write", -1);
		printf("\n");
	}
	//关闭本次连接
	close(fd);
	return 0;
}