栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 系统运维 > 运维 > Linux

高并发服务器epoll

Linux 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

高并发服务器epoll

文章目录
      • epoll操作函数
        • epoll_ctl
        • epoll_wait
      • ET模式和LT模式的区别:
      • epoll优缺点

epoll操作函数

epoll_create:创建监听红黑树

#include 

// 驱使内核创建一棵epoll监听使用的 红黑树
int epoll_create(int size);
	size:指定创建的树所能挂载的fd个数。(仅供内核参考)
	返回值:
		成功:指向红黑树树根的 fd
		失败:-1, errno
epoll_ctl
    // 操作待监听、解除监听的 fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
	epfd: epoll_create函数返回值。
	op:操作:
		EPOLL_CTL_ADD:添加 fd 到监听红黑树
		EPOLL_CTL_MOD:修改监听红黑树一个 fd 属性。
		EPOLL_CTL_DEL:将 fd 从监听红黑树上摘下
    fd:待操作的 文件描述符
    event:描述fd监听事件(结构体地址)。
    	struct epoll_event {
               uint32_t     events;      
               epoll_data_t data;        
        };
		events成员:fd对应的监听事件:
			EPOLLIN(读)
			EPOLLOUT(写)
			EPOLLERR(异常)
		data成员:(联合体、共用体)
            int          fd;
			void        *ptr;
struct test {
    int fd;
    void *call_back(fd, void *arg);
    ....
} *ptr;
            uint32_t     u32;
            uint64_t     u64;
	返回值:
		成功:0
		失败:-1, errno	
epoll_wait
// 阻塞监听 epfd 红黑树上的fd 是否满足对应监听事件。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
	epfd: epoll_create函数返回值。
    events: 传出【数组】。传出的数组中,包含所有满足监听条件的fd及对应结构体。 
    maxevents:数组容量。
    timeout:超时。
    	-1:阻塞。
    	0:非阻塞
    	>0:超时时间(毫秒)
    返回值:
    	>0: 满足对应监听条件的文件描述符个数。用作 for 循环上限。
		0: 超时到达,没有满足条件的fd。
		-1: 错误,errno.

EPOLL事件有两种模型:
Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发只要有数据都会触发。

ET模式和LT模式的区别:

LT 模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用 epoll_wait 时,会再次响应应用程序并通知此事件。

ET 模式:当 epoll_wait 检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用 epoll_wait 时,不会再次响应应用程序并通知此事件。

ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

  • ET:边沿触发。 只支持 非阻塞模式。

    • fd 对应的缓冲区中,剩余数据不会触发 epoll_wait() 返回。
      • 设置方法:
        • int flg = fcntl(cfd, F_GETFL)
        • flg |= O_NONBLOCK;
        • fcntl(cfd, F_SETFL, flg);
        • event.events = EPOLLIN | EPOLLET
  • LT:水平触发(默认)

    • fd 对应的缓冲区中,剩余数据会触发 epoll_wait() 返回。
  • 结论

    • epoll 边沿触发模式固定用法:
      • 非阻塞 + 忙轮询while() + EPOLLIN | EPOLLET
epoll优缺点
  • epoll 的优点主要是一下几个方面:

1)监视的描述符数量不受限制,它所支持的 FD 上限是最大可以打开文件的数目,这个数字一般远大于 2048,举个例子,在 1GB 内存的机器上大约是 10 万左右,具体数目可以 cat /proc/sys/fs/file-max 察看,一般来说这个数目和系统内存关系很大。select() 的最大缺点就是进程打开的 fd 是有数量限制的。这对于连接数量比较大的服务器来说根本不能满足。虽然也可以选择多进程的解决方案( Apache 就是这样实现的),不过虽然 Linux 上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。

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

3)select(),poll() 每次调用都要把 fd 集合从用户态往内核态拷贝一次,而 epoll 只要一次拷贝,这也能节省不少的开销。

代码参考

#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
int main(int argc, char *argv[])
{
 
	int ret;
	int fd;
	
	ret = mkfifo("test_fifo", 0666); // 创建有名管道
	if(ret != 0){
		perror("mkfifo:");
	}
	
	fd = open("test_fifo", O_RDWR); // 读写方式打开管道
	if(fd < 0){
		perror("open fifo");
		return -1;
	}
	
	ret = 0;
	struct epoll_event event;	// 告诉内核要监听什么事件
	struct epoll_event wait_event;
	
	
	int epfd = epoll_create(10); // 创建一个 epoll 的句柄,参数要大于 0, 没有太大意义
	if(-1 == epfd ){
		perror ("epoll_create");
		return -1;
    }
	
	event.data.fd = 0; 	   // 标准输入
	event.events = EPOLLIN; // 表示对应的文件描述符可以读
	
	// 事件注册函数,将标准输入描述符 0 加入监听事件
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
	if(-1 == ret){
		perror("epoll_ctl");
		return -1;
    }
	
	event.data.fd = fd; 	// 有名管道
	event.events = EPOLLIN; // 表示对应的文件描述符可以读
	
	// 事件注册函数,将有名管道描述符 fd 加入监听事件
	ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
	if(-1 == ret){
		perror("epoll_ctl");
		return -1;
    }
	
	ret = 0;
	
	while(1){			
		// 监视并等待多个文件(标准输入,有名管道)描述符的属性变化(是否可读)
		// 没有属性变化,这个函数会阻塞,直到有变化才往下执行,这里没有设置超时
		ret = epoll_wait(epfd, &wait_event, 2, -1);
		//ret = epoll_wait(epfd, &wait_event, 2, 1000);
		
		if(ret == -1){ // 出错
			close(epfd);
			perror("epoll");
		}else if(ret > 0){ // 准备就绪的文件描述符
		
			char buf[100] = {0};
			
			if( ( 0 == wait_event.data.fd ) 
			&& ( EPOLLIN == wait_event.events & EPOLLIN ) ){ // 标准输入
			
				read(0, buf, sizeof(buf));
				printf("stdin buf = %sn", buf);
				
			}else if( ( fd == wait_event.data.fd ) 
			&& ( EPOLLIN == wait_event.events & EPOLLIN ) ){ // 有名管道
			
				read(fd, buf, sizeof(buf));
				printf("fifo buf = %sn", buf);		
			}		
		}else if(0 == ret){ // 超时
			printf("time outn");
		}
	}
	
	close(epfd);
	
	return 0;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/835131.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号