高并发服务器epoll

epoll操作函数

epoll_create:创建监听红黑树

#include <sys/epoll.h>

// 驱使内核创建一棵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 events */
               epoll_data_t data;        /* User data variable */
        };
		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 <sys/epoll.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
 
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 = %s\n", buf);
				
			}else if( ( fd == wait_event.data.fd ) 
			&& ( EPOLLIN == wait_event.events & EPOLLIN ) ){ // 有名管道
			
				read(fd, buf, sizeof(buf));
				printf("fifo buf = %s\n", buf);		
			}		
		}else if(0 == ret){ // 超时
			printf("time out\n");
		}
	}
	
	close(epfd);
	
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用epoll和线程池实现高并发服务器的C++11代码示例: ```cpp #include <iostream> #include <thread> #include <vector> #include <queue> #include <mutex> #include <condition_variable> #include <sys/epoll.h> #include <unistd.h> #define MAX_EVENTS 100 #define THREAD_POOL_SIZE 10 std::mutex mtx; std::condition_variable cv; std::queue<int> taskQueue; void workerThread() { while (true) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [] { return !taskQueue.empty(); }); int fd = taskQueue.front(); taskQueue.pop(); // 处理任务,这里可以根据具体需求进行处理 lock.unlock(); // 继续监听其他事件 } } int main() { // 创建epoll句柄 int epoll_fd = epoll_create(1); if (epoll_fd == -1) { std::cerr << "Failed to create epoll" << std::endl; return 1; } // 创建线程池 std::vector<std::thread> threadPool; for (int i = 0; i < THREAD_POOL_SIZE; ++i) { threadPool.emplace_back(workerThread); } // 添加监听事件到epoll句柄 struct epoll_event event; event.events = EPOLLIN; event.data.fd = /* 监听的文件描述符 */; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, /* 监听的文件描述符 */, &event) == -1) { std::cerr << "Failed to add event to epoll" << std::endl; return 1; } // 开始监听事件 struct epoll_event events[MAX_EVENTS]; while (true) { int num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (num_events == -1) { std::cerr << "Failed to wait for events" << std::endl; return 1; } for (int i = 0; i < num_events; ++i) { if (events[i].events & EPOLLIN) { // 处理读事件,将任务添加到任务队列 std::lock_guard<std::mutex> lock(mtx); taskQueue.push(events[i].data.fd); cv.notify_one(); } } } // 清理资源 close(epoll_fd); for (auto& thread : threadPool) { thread.join(); } return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值