Linux并发网络编程模型
最好的参考资料:
1.师从互联网。
2.man 7 epoll
3.http://bbs.chinaunix.net/thread-1740209-1-1.html
4.http://hi.baidu.com/firobd/blog/item/dcb4f251530d341d0cf3e3ee.html
5.http://www.cnblogs.com/dubingsky/archive/2009/07/22/1528695.html
6.http://bbs.chinaunix.net/thread-1740209-2-1.html
7.http://www.cppblog.com/converse/archive/2008/04/29/48482.html
第一条:概述
简单说来 epoll 就是Linux内核解决大量的用户并发地连接到服务器上,而为程序员提供的开发大规模并发网络程序的一种I/O 多路复用技术。epoll是Linux下多路复用IO接口select/poll的增强版本,本质上仍是I/O多路复用技术,所以他没什么好怕的,但是epoll能力却非常强悍的。你会见到如此少的代码却能轻松的搞定如何接受大量用户连接的问题,非常厉害。
最早你可你在Kernel2.5.44中见到epoll的系统调用,2.6内核时被正式引进到glibc2.3.2中。epoll是linux特有的机制 类似与BSD的Kqueue和Solaris的/dev/poll。
第二条:Linux并发网络编程模型(0)Apache 模型,简称 PPC ( Process Per Connection ,):为每个连接分配一个进程。主机分配给每个连接的时间和空间上代价较大,并且随着连接的增多,大量进程间切换开销也增长了。很难应对大量的客户并发连接。
(1) TPC 模型( Thread Per Connection ):每个连接一个线程。和PCC类似。
(2) select 模型:I/O多路复用技术。
(2.1)每个连接对应一个描述。select模型受限于 FD_SETSIZE即进程最大打开的描述符数linux2.6.35为1024,实际上linux每个进程所能打开描数字的个数仅受限于内存大小,然而在设计select的系统调用时,却是参考FD_SETSIZE的值。可通过重新编译内核更改此值,但不能根治此问题,对于百万级的用户连接请求 即便增加相应 进程数, 仍显得杯水车薪呀。
(2.2)select每次都会扫描一个文件描述符的集合,这个集合的大小是作为select第一个参数传入的值。但是每个进程所能打开文件描述符若是增加了 ,扫描的效率也将减小。
(2.3)内核到用户空间,采用内存复制传递文件描述上发生的信息。
(3)poll 模型:I/O多路复用技术。poll模型将不会受限于FD_SETSIZE,因为内核所扫描的文件 描述符集合的大小是由用户指定的,即poll的第二个参数。但仍有扫描效率和内存拷贝问题。
(4)pselect模型:I/O多路复用技术。同select。
(5)epoll模型:
(5.1)无文件描述字大小限制仅与内存大小相关
(5.2)epoll返回时已经明确的知道哪个socket fd发生了什么事件,不用像select那样再一个个比对。
(5.3)内核到用户空间采用共享内存方式,传递消息。
第三条:epoll API#include <sys/epoll.h>//epoll机制相关的所需的API和数据类型都在这个头文件中
(0)int epoll_create (int size) ;
(0.1)函数返回一个epoll专用的描述符epfd,epfd引用了一个新的epoll机制例程(instance.)。
(0.2)参数size是这个epoll专用描述符epfd所关联的socketfd的最大个数。man手册指出:The size is not the maximum size of the backing store but just a hint to the kernel about how to dimension internal structures. (Nowadays, size is ignored; see NOTES below.)从2,6.8内核就不使用这个参数,而是内核动态分配所需的数据结构。
(0.3)当我们不再需要epfd时,一定要调用close关闭他。当epfd被关闭时,kernel销毁所引用的instance和释放相关资源。
(1) int epoll_create1(int flags);
(1.1)正如man手册所指出的epoll_create的参数size,其实已经被抛弃了,毫无用处。epoll_create1和epoll_create功效一样,而且还添加了一个flags参数,其值如下:
enum {
EPOLL_CLOEXEC = 02000000,//在新建的epfd上设置FD_CLOEXEC。
EPOLL_NONBLOCK = 04000 //新建的epfd设置为非阻塞。
};
(2)int epoll_ctl (int __epfd, int __op, int __fd, struct epoll_event *__event) ;//设置epfd关联的一个socketfd
(2.1)参数epfd的值设置为epoll_create返回的epoll专用描述符。
(2.2)参数op值如下:
#define EPOLL_CTL_ADD 1 /* Add a file decriptor to the interface. */关联一个socketfd到epfd
#define EPOLL_CTL_DEL 2 /* Remove a file decriptor from the interface. */删除一个epfd已经关联的socketfd
#define EPOLL_CTL_MOD 3 /* Change file decriptor epoll_event structure. */更新一个epfd已经关联的socketfd
(2.3)参数fd设置为我们要操作的socketfd
(2.4) 参数event具体如何设置socketfd,其值如下:
typedef union epoll_data {//注意这是union结构~~~
void *ptr;
int fd;//一般都用这个成员
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {//呵呵,这个数据结构就是epoll为什么如此高效的原因。
uint32_t events; /* Epoll events *///对应的时间
epoll_data_t data; /* User data variable *///此值一般是关心的socketfd
};
成员events对应的值如下,他表示相应的描述符发生的事件或状态:
enum EPOLL_EVENTS
{
EPOLLIN = 0x001,//可读
EPOLLPRI = 0x002,//有紧急数据可读,比如带外数据
EPOLLOUT = 0x004,//可写
EPOLLRDNORM = 0x040,
EPOLLRDBAND = 0x080,
EPOLLWRNORM = 0x100,
EPOLLWRBAND = 0x200,
EPOLLMSG = 0x400,
EPOLLERR = 0x008,//出错
EPOLLHUP = 0x010,//挂断
EPOLLRDHUP = 0x2000,//连接断开,或处于半关闭状态(前提是对应的流socket,就是支持连接的socket)。man手册中的说明:(since Linux 2.6.17) Stream socket peer closed connection, or shut down writing half of connection. (This flag is especially useful for writing simple code to detect peer shutdown when using Edge Triggered monitoring.)
EPOLLONESHOT = (1 << 30),/*默认监听一个socketfd之后并不把它从epfd关联的socketfd集合中删除,只清空socketfd对应的事件成员的值一次事件。EPOLLONESHOT表示设置socketfd为监听一次事件。当监听完这次事件之后,从epfd关联的socketfd集合中删除监听的socketfd,如果还需要继续监听这个socketfd的话,需要再次把这个socketfd加入到epfd关联的socketfd集合(队列)里 */
EPOLLET = (1 << 31)// 将epfd设为边缘触发(Edge Triggered)模式,默认epfd是电平触发(Level Triggered)。 下文细述。
};
(3)int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);//想想pselect
(3.1)参数events指向一个struct epoll_event类型的数组,内核用他来装载发生的事件传送给用户。
(3.2)参数maxevents指明数组的大小。
(3.3)参数timeout:超时时间。
(3.4)参数sigmask:信号屏蔽集。epoll_pwait等价如下:
sigset_t origmask;
sigprocmask(SIG_SETMASK, &sigmask, &origmask);
ready = epoll_wait(epfd, &events, maxevents, timeout);
sigprocmask(SIG_SETMASK, &origmask, NULL);
epoll_wait等待epfd关联的socketfd上发生事件,如果发生了内核把发生事件的socketfd和事件类型放到events数组当中(就是这个小小的细节就决定了epoll强悍的性能,因为他不需要像select再轮询所有的socketfd集合,去确定哪个socketfd 要去处理。同时内核也会将epfd关联的socketfd的struct evpoll_event结构的事件类型成员(events)清空(不是epoll_wait函数的第二个参数events,要严重区分),所以如果下次你还要关注这个socketfd就需要用epoll_ctl的EPOLL_CTL_MOD(不是EPOLL_CTL_ADD,socketfd并未清空,只是事件类型清空)命令来重新设置我们关心socketfd的事件类型。重新设置这一步非常重要!!!
第四条:epoll工作模式epoll有两种工作方式
ET:Edge Triggered,边缘触发。仅当状态发生变化时才会通知,epoll_wait返回。换句话,就是对于一个事件,只通知一次。且只支持非阻塞的socket。
LT:Level Triggered,电平触发(默认工作方式)。类似select/poll,只要还有没有处理的事件就会一直通知,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll.支持阻塞和不阻塞的socket。
第五条:FAQ 来自互联网,链接见上面
1、单个epoll并不能解决所有问题,特别是你的每个操作都比较费时的时候,因为epoll是串行处理的。 所以你有还是必要建立线程池来发挥更大的效能。
2、如果fd被注册到两个epoll中时,如果有时间发生则两个epoll都会触发事件。
3、如果注册到epoll中的fd被关闭,则其会自动被清除出epoll监听列表。
4、如果多个事件同时触发epoll,则多个事件会被联合在一起返回。
5、epoll_wait会一直监听epollhup事件发生,所以其不需要添加到events中。
6、为了避免大数据量io时,et模式下只处理一个fd,其他fd被饿死的情况发生。linux建议可以在fd联系到的结构中增加ready位,然后epoll_wait触发事件之后仅将其置位为ready模式,然后在下边轮询ready fd列表。本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/firo_baidu/archive/2011/01/27/6167158.aspx