epoll
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术,按照man手册的说法:是为处理大批量句柄而作了改进的poll。epoll的特点:epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的。
同 I/O多路复用和信号驱动I/O一样,Linux的epoll(event poll)API可以检查描述符上的I/O就绪状态。
fd(文件描述符)
select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要
::表示全局,这样可以声明全局函数
bool TTcpServer::Listen(const unsigned short port)
{
if (m_IsListen)
return false;
//socket
int accept_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP);
if (accept_fd < 0)
return false;
//端口重用,快速重启服务
int opt = 1;
::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//bind
sockaddr_in addr;
memset(&addr, 0, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
::inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);
if (0 != ::bind(accept_fd, (sockaddr*)&addr, (socklen_t)sizeof(sockaddr_in)))
{
perror("bind failed");
return false;
}
//listen
if (0 != ::listen(accept_fd, SOMAXCONN))
{
perror("listen failed");
return false;
}
//add epoll
epoll_event event;
event.data.u64 = 0; //清空数据
event.data.fd = accept_fd; //绑定client指针
event.events = POLLIN | POLLPRI;
::epoll_ctl(m_Poll_Fd, EPOLL_CTL_ADD, accept_fd, &event);
//启动accept工作线程
Start();
//启动eventloop工作线程
for(std::vector<TEventLoop*>::iterator iter = m_EventLoops.begin(); iter != m_EventLoops.end(); iter++)
(*iter)->Start();
//end
m_IsListen = true;
return true;
}
2.1 socket函数
int socket(int protofamily, int type, int protocol);
socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。
注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。
epoll API的核心数据结构称作epoll实例,它和一个打开的文件描述符相关联。这个文件描述符不是用来做I/O操作的,相反,它是内核数据结构的句柄,这些内核数据结构实现了两个目的。
- 记录了在进程中声明过的感兴趣的文件描述符列表——兴趣列表。
- 维护了处于I/O就绪态的文件描述符列表——就绪列表。
对于由epoll检查的每一个文件描述符,我们可以指定一个位掩码来表示我们感兴趣的事件。
epoll API由以下3个系统调用组成。
- 系统调用epoll_creat()创建一个epoll实例,返回代表该实例的文件描述符。
- 系统调用epoll_ctl()操作同epoll实例相关联的兴趣列表。通过epoll_ctl(),我们可以增加新的描述符到列表中,将已有的文件描述符从该列表中移除,以及修改代表描述符上事件类型的位掩码。
- 系统调用epoll_wait()返回与epoll实例相关联的就绪列表中的成员。
/*创建一个epoll实例。为新实例返回一个fd。
“size”参数是指定文件数量的提示
与新实例相关联的描述符。fd的
epoll_create()返回的值应该用close()关闭。*/
/*与epoll_create相同,但带有标志参数。未使用的尺寸
参数被删除。*/
作为函数返回值,epoll_create()返回了代表新创建的epoll实例的文件描述符。这个文件描述符在其他几个epoll系统调用中用来表示epoll实例。当这个文件描述符不再需要时,应该通过close()来关闭。当所有与epoll实例相关的文件描述符都被关闭时,实例被销毁,相关的资源都返还给系统。(多个文件描述符可能引用到相同的epoll实例,这是由于调用了fork()或者dup()这样类似的函数所致。)
从2.6.27版内核以来,Linux支持了一个新的系统调用epoll_create1()。该系统调用执行的任务同epoll_create()一样,但是去掉了无用的参数size,并增加了一个可用来修改系统调用行为的flags参数。目前只支持一个flag标志:EPOLL_CLOEXEC,它使得内核在新的文件描述符上启动了执行即关闭标志。
2.修改epoll的兴趣列表:epoll_ctl()
系统调用epoll_ctl()能够修改由文件描述符epfd所代表的epoll实例中的兴趣列表。