day4 多路复用epoll函数族

目录

epoll函数族介绍

epoll_event结构体

epoll服务期实现

server.c

select,poll和epoll各自优缺点

select

poll

epoll


epoll函数族介绍

/*创建epoll句柄*/
int epoll_create(int size);  //size参数实际上已经被弃用
/*epoll句柄的控制接口*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

 参数:

epfd: epoll 专用的文件描述符,epoll_create()的返回值

op: 表示动作,用三个宏来表示:

        EPOLL_CTL_ADD:注册新的 fd 到 epfd 中;

        EPOLL_CTL_MOD:修改已经注册的fd的监听事件;

         EPOLL_CTL_DEL:从 epfd 中删除一个 fd;

fd: 需要监听的文件描述符

event: 告诉内核要监听什么事件

epoll_event结构体

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

struct epoll_event {
   __uint32_t events; /* Epoll events */
   epoll_data_t data; /* User data variable */
};

EPOLLIN :表示对应的文件描述符可以读(包括对端 SOCKET 正常关闭);

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

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

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

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

EPOLLET :将 EPOLL 设为边缘触发(Edge Trigger)模式,这是相对于水平触发(Level Trigger)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列里
 

/*等待 epoll 文件描述符上的 I/O 事件*/
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

 参数:

epfd: epoll 专用的文件描述符,epoll_create()的返回值

events: 分配好的 epoll_event 结构体数组,epoll_wait 将会把发生的事件赋值到events 数组中

maxevents: events 数组的元素个数

timeout: 超时时间,单位为毫秒,为 -1 时,函数为阻塞
 

epoll服务期实现

server.c

#include "net.h"
#include 

#define MAX_SOCK_FD 1024

int main(int argc, char *argv[])
{
	int i, nfds, fd, epfd, newfd;
	Addr_in addr;
	socklen_t addrlen = sizeof(Addr_in);
	struct epoll_event tmp, events[MAX_SOCK_FD] = {};
	/*检查参数,小于3个 直接退出进程*/
	Argment(argc, argv);
	/*创建已设置监听模式的套接字*/
	fd = CreateSocket(argv);

	if( (epfd = epoll_create(1)) < 0)
		ErrExit("epoll_create");
	tmp.events = EPOLLIN;
	tmp.data.fd = fd;
	if( epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &tmp) )
		ErrExit("epoll_ctl");

	while(1) {
		if( (nfds = epoll_wait(epfd, events, MAX_SOCK_FD, -1) ) < 0)
			ErrExit("epoll_wait");
		printf("nfds = %d\n", nfds);

		for(i = 0; i < nfds; i++) {
			if(events[i].data.fd == fd){
				/*接收客户端连接,并生成新的文件描述符*/
				if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)
					perror("accept");
				printf("[%s:%d] connection.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
				tmp.events = EPOLLIN;
				tmp.data.fd = newfd;
				if( epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &tmp) )
					ErrExit("epoll_ctl");
			}else{/*处理客户端数据*/
				if(DataHandle(events[i].data.fd) <= 0){
					if( epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL) )
						ErrExit("epoll_ctl");
					if( getpeername(events[i].data.fd, (Addr *)&addr, &addrlen) )
						perror("getpeername");
					printf("[%s:%d] exited.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );
					close(events[i].data.fd);
				}
			}
		}
	}
	close(epfd);
	close(fd);
	return 0;
}

select,poll和epoll各自优缺点

select

1. 单个进程能够监视的文件描述符的数量有最大限制,通常是1024,虽然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差

2. 内核/用户空间内存拷贝问题,select需要复制大量的句柄数据结构,会产生巨大的开销

3. select返回的是含有整个句柄的数组,应用程序**需要遍历整个数组才能发现哪些句柄发生了事件

4. select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行I/O操作,那么之后每次select调用还是会将这些文件描述符通知进程

5. 内核中实现select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄

假设服务器需要支持100万的并发连接,在`__FD_SETSIZE`为`1024`的情况下,则我们至少需要开辟1000个进程才能实现100万的并发连接

poll

poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在

select与poll目前在小规模服务器上还是有用武之地,并且维护老系统代码的时候,经常会用到这两个函数;

epoll

epoll是Linux下多路复用I/O接口select/poll的增强版本

epoll只需要监听那些已经准备好的队列集合中的文件描述符,效率较高

你可能感兴趣的:(LV9,网络编程及实战(下)),我的小白学习笔记,tcp/ip,linux,c语言,服务器,网络)