首页未分类 › Linux socket编程(四) 简单聊天室之epoll版

Linux socket编程(四) 简单聊天室之epoll版

这一篇我们用epoll改写之前写的简单聊天室,Epoll是Linux内核为处理大批量句柄而作了改进的poll。

我们要用到epoll的三个函数,分别是:

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

下面对要用到epoll的操作进行封装

Epoll.h

Socket类的实现见我的这篇博文Linux socket编程(一) 对套接字操作的封装

更好一点的做法是把Socket类做成一个共享函数库

Epoll.cpp

 

现在考虑如何把epol用到socket的通信中

参考了这篇博文 http://www.cnblogs.com/OnlyXP/archive/2007/08/10/851222.html

epoll有两种触发模式:

LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

ET(edge-triggered) 是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致 了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

接下来我们使用边沿触发这种方式(ET),先看一下手册是怎么说的(man epoll):

Q9 Do I need to continuously read/write a file descriptor until EAGAIN when

意思大概是说当使用ET这种方式时,要不断地对文件描诉符进行读/写,直至遇到EAGAIN为止。

为什么要这样呢:

假 如发送端流量大于接收端的流量 (意思是epoll所在的程序读比转发的socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发 给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,同样对 于recv函数也要进行相应的封装。

以下是我的封装:(英文注释写的不是很好,大家凑合着看吧)

好了接下来是Socket类的派生类,EpollServerSocket类

EpollServerSocket.h

以下是EpollServerSocket类的实现

(以前写的客户端不用更改,直接可以与这个服务器通信)

对于大数据量的传输,很明显要不断地进行读/写,这样就会出现长时间的阻塞,甚至成为系统的性能瓶颈

但是对于只有较少活跃的socket,同时数据量较小的情况,epoll的效率应该是比select和poll高的(呃,不过没有很好的测试过)

不过好像有一种做法可以避免阻塞,就是利用EPOLLOUT事件

“EPOLLOUT事件的意思就是 当前这个socket的发送状态是空闲的,此时处理能力很强,告知用户可以发送数据。
所以在正常情况下,基本上socket在epoll_wait后,都会得到一个socket的EPOLLOUT事件。

【如果你不是一直在写数据或者你不是在传送一个几百M的数据文件,send一半都处于空闲状态】
而这个特性刚好可以处理 阻塞问题。
当数据发送不出去的时候,说明网络阻塞或者延迟太厉害了。
那么将要发送的数据放在一个buffer中,当下次你发现了EPOLLOUT事件时,说明现在网络处于空闲状态,OK,此时你可以用另外一个线程来发送上次堆积在buffer中的数据了。这样就不会阻塞了“

发表评论