epoll边沿模式---自言自语

本文详细介绍了使用epoll进行网络编程的方法,包括监控文件描述符、处理读写事件及包的接收发送流程,并探讨了不同场景下的优化策略,如高频小包、长连接等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

首先创建epoll对象,
把要监控的文件描述符添加进去(要指明你要监控这个文件描述符的什么事件,文件描述符添加之前要设置为非阻塞的),
假设我们监控可读事件,
事件到达—说明缓冲区里有数据了,我们可以去读了,
首先我们读出前几个字节,这是我自己约定好的来确定包的总长度,当然也可以定长或者采用特殊结束字段什么的来判断。
用一个循环来读,知道返回值为和等于包长度或者-1(errno为EAGAIN或者EWOULDBLOCK),
(需要特别处理的是,当总长度大于设定的socketbuffer最大值时,我们本次最多只能读到bufferMax这个值,也就说明在本次事件我们不能接完完整的包,我们把这个事件fd和收到的数据放到队列中,另外如果除了包长度之外还有多余的数据怎么处理?会不会出现两个包粘混在一起的情况?)
如果我们获得了完整的包,我先不改变对这个fd的监控,它还是被监控着是否可读,
根据包内容做逻辑处理,确认回包内容后,把回包放到输出队列,改变对这个fd的监控为是否可写,
当监控到fd可写的事件后,从输出队列中取出回包内容,
在一个循环中不断的写直到累计等于要写的包长度或者-1(errno为EAGAIN或者EWOULDBLOCK),
如果本次没写完要改变记录状态,等待该fd的下一次可写事件的发生继续完成写操作,当写完成后,改变对该fd的监控事件为是否可读.

上面这个过程是一个理想过程,包内容逻辑处理可能是一个阻塞的过程,这会带来一系列的问题,如果此时的epoll和这个逻辑处理不在一个线程,那么此时该fd再次收到第二个包数据会发生什么?(如果在一个线程epoll的优势好像就被破坏了)

此外读写需不需要单独到各自的线程中去处理,或者每个fd都分离出各自的独立线程处理读写和回包逻辑?
第一种单独的读一个线程,写一个线程,不断的处理读队列和写队列里的内容,读队列生成buffer事件添加到包事件队列交给回包逻辑线程去处理,处理完生成回包添加到写队列,结构上比较清晰,可以根据负荷调整各部分的线程数目;需要考虑的是读写线程可能的空转比例
第二种每增加一个fd就拉一个线程或者干脆采用线程池,感觉占用的内存资源会多,会大大增加cpu压力,划不划算,主要看回包逻辑复杂程度;
第三种由于这里的读写本来就是非阻塞的所以读写和epoll事件监视循环在一块,epoll的timeout设为0,这种处理少开了线程也少了好几处的资源竞争问题但是感觉总体结构不是太理想。

高频小包,
高频大包,
长间隔包,
长连接,
短连接,
socketbuffer的大小设置,
是否可以采用进程加速扩容,
是否可以多ip扩容,
空闲处理,
线程管理,
回包逻辑是否复杂

### epoll 边沿触发模式 (Edge Triggered, ET) 的工作原理 在边沿触发模式下,内核仅在状态发生变化时通知应用程序。具体来说: - 当文件描述符从未准备好变为准备好的瞬间,会触发一次事件。 - 如果文件描述符已经处于就绪状态,则不会再次触发事件。 这种机制意味着,在ET模式下,一旦`epoll_wait`返回某个文件描述符已准备好读/写操作之后,直到该文件描述符的状态发生改变之前(即不再有新的数据到达或可写空间释放),不会再收到关于此文件描述符的通知[^2]。 为了确保能够完全处理所有可用的数据而不遗漏任何部分,在接收到一个事件后应当尽可能多地执行I/O操作直至遇到EAGAIN错误为止。这通常通过循环调用来实现,比如对于读取操作而言就是不断尝试读取直到无法再读出更多数据[^4]。 ### 使用方法示例 下面是一个简单的Python代码片段展示了如何设置并使用epoll边沿触发模式来监听套接字上的输入事件: ```python import socket import select def create_epoll_socket(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) epfd = select.epoll() # 设置为非阻塞模式 sock.setblocking(False) # 将socket添加到epoll实例中,并指明采用边沿触发模式 event_mask = select.EPOLLIN | select.EPOLLET epfd.register(sock.fileno(), event_mask) return sock, epfd sock, epfd = create_epoll_socket() try: while True: events = epfd.poll() # 阻塞等待事件 for fd, event in events: if event & select.EPOLLIN: data = b'' try: while True: part = sock.recv(4096) if not part or len(part) < 4096: break data += part except BlockingIOError as e: pass finally: epfd.close() sock.close() ``` 在这个例子中,创建了一个TCP服务器套接字并将它注册到了一个新的epoll对象上,同时设置EPOLLET标志表示启用边沿触发模式。每当检测到新到来的数据包时,程序会在内部循环里反复尝试接收数据,直到捕获到BlockingIOError异常表明当前没有更多的数据可供读取[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值