网络编程之epoll

大耗子 2020年02月24日 188次浏览

文章链接:https://codemouse.online/archives/2020-02-24153745

epoll结构体的定义:

struct epoll_event {
	__uint32_t events;      /* epoll event */
	epoll_data_t data;      /* User data variable */
};
typedef union epoll_data {
	void *ptr;
	int fd;
	__uint32_t u32;
	__uint64_t u64;
} epoll_data_t;//保存触发事件的某个文件描述符相关的数据

工作原理

epoll是在唯一个红黑树和一个就绪链表.

内核不仅会将事件存放到红黑树中,还会将所有的epoll对象与设备驱动程序(中断处理程序)建立回调关系(注册回调函数),当一个事件的中断到达,就把事件存放到链表中。所以当一个事件就绪时,内核把网卡上的数据拷贝到内核中后就把事件存放到就绪链表中,这样,一棵红黑树,一个链表就解决了大量并发的描述符问题。

epoll的创建

  • 创建一个epoll的fd, size用来告诉内核这个监听的数目一共有多大

    int epoll_create(int size);

  • 绑定fd的事件的类型

    struct epoll_event ep_ev;
    ep_ev.events = EPOLLIN;//数据的读取
    ep_ev.data.fd = listen_sock;

  • events的宏的集合:

    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
    EPOLLOUT:表示对应的文件描述符可以写;
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示对应的文件描述符发生错误;
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

  • 添加epoll的事件,并设置事件的类型

    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ep_ev);
    EPOLL_CTL_ADD:注册新的fd到epfd中;
    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
    EPOLL_CTL_DEL:从epfd中删除一个fd

  • 轮循事件

    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    返回值是发生事件的个数
    struct epoll_event ready_ev[128];//申请空间来放就绪的事件。
    int maxnum = 128;
    int timeout = 1000;
    epoll_wait(epoll_fd,ready_ev,maxnum,timeout);

  • 判断是哪种事件

    ready_ev[i].events & EPOLLIN

  • 获取fd

    int fd = ready_ev[i].data.fd;

  • 使用结束记得要close掉fd,防止资源的浪费

    close(fd);

注意

添加事件的时候,尽量使用EPOLLET边缘触发,防止在多线程的时候,多次出发,重复读

epoll demo

void epoll_use(int listen_sock){
	int epoll_fd = epoll_create(256);
    if(epoll_fd < 0){
        perror("epoll creat");
        exit(6);
    }

    struct epoll_event ep_ev;
    ep_ev.events = EPOLLIN;//数据的读取
    ep_ev.data.fd = listen_sock;

    //添加关心的事件
    if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ep_ev) < 0){
        perror("epoll_ctl");
        exit(7);
    }

    struct epoll_event ready_ev[128];//申请空间来放就绪的事件。
    int maxnum = 128;
    int timeout = 1000;//设置超时时间,若为-1,则永久阻塞等待。
    int ret = 0;
    
    int done = 0;
    while(!done){
		ret = epoll_wait(epoll_fd,ready_ev,maxnum,timeout);
		if(ret == -1)
		{
			perror("epoll_wait");
			continue;
		}
		else if(ret == 0)
		{
			printf("time out...\n");
			continue;
		}
		//遍历events数组
        int i = 0;
		for(;i < ret;++i){
			//判断是否为监听套接字,是的话accept
			int fd = ready_ev[i].data.fd;
			if((fd == listen_sock) && (ready_ev[i].events & EPOLLIN)){
				struct sockaddr_in remote;
				socklen_t len = sizeof(remote);

				int accept_sock = accept(listen_sock,(struct sockaddr*)&remote,&len);
				if(accept_sock < 0){
					perror("accept");
					continue;
				}
				printf("accept a client..[ip]: %s,[port]: %d\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port));
				//将新的事件添加到epoll集合中
				ep_ev.events = EPOLLIN | EPOLLET;
				ep_ev.data.fd = accept_sock;

				set_noblock(accept_sock);

				if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,accept_sock,&ep_ev) < 0){
					perror("epoll_ctl");
					close(accept_sock);
				}
			}
			else{//普通IO
				 if(ready_ev[i].events & EPOLLIN){
					 //申请空间同时存文件描述符和缓冲区地址

					 char buf[102400];
					 memset(buf,'\0',sizeof(buf));

					 ssize_t _s = recv(fd,buf,sizeof(buf)-1,0);
					 if(_s < 0){
						 perror("recv");
						 continue;
					 }else if(_s == 0){
						 printf("remote close..\n");
						//远端关闭了,进行善后
						 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
						 close(fd);
					 }else{
						 //读取成功,输出数据
						 printf("client# %s",buf);
						 fflush(stdout);

						 //将事件改写为关心事件,进行回写
						 ep_ev.data.fd = fd;
						 ep_ev.events = EPOLLOUT | EPOLLET;

						 //在epoll实例中更改同一个事件
						 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ep_ev);
					 }
				 }else if(ready_ev[i].events & EPOLLOUT){
					 const char*msg = "hello client\n";
					 send(fd,msg,strlen(msg),0);
					 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
					 close(fd);
				}
			}
		}
        
    }
    close(listen_sock);
}