l  epoll是什么?

epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术

Linux下设计并发网络程序,常用的模型有:

  • Apache模型(Process Per Connection,简称PPC)
  • TPC(Thread PerConnection)模型
  • select模型和poll模型。
  • epoll模型

l  常用模型的缺点

n  PPC/TPC模型

这两种模型思想类似,就是让每一个到来的连接都有一个进程/线程来服务。这种模型的代价是它要时间和空间。连接较多时,进程/线程切换的开销比较大。因此这类模型能接受的最大连接数都不会高,一般在几百个左右。

n  select模型

  • 最大并发数限制:因为一个进程所打开的fd(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此select模型的最大并发数就被相应限制了。
  • 效率问题:select每次调用都会线性扫描全部的fd集合,这样效率就会呈现线性下降,把FD_SETSIZE改大可能造成这些fd都超时了。
  • 内核/用户空间内存拷贝问题:如何让内核把fd消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

n  poll模型

  • 基本上效率和select是相同的,select缺点的2和3它都没有改掉。

l  epoll的改进

对比其他模型的问题,epoll的改进如下:

n  epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。

n  效率提升,Epoll最大的优点就在于它只管你活跃的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。

n  内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。

l  epoll为什么高效

epoll的高效和其数据结构的设计是密不可分的。

首先回忆一下select模型,当有I/O事件到来时,select通知应用程序有事件到了,应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件;代码像下面这样:

int  res = select(maxfd+1, &readfds, NULL, NULL, 120);

if (res > 0)

{

for (int i = 0; i < MAX_CONNECTION; i++)

{

if (FD_ISSET(allConnection[i],&readfds))

{

handleEvent(allConnection[i]);

}

}

}

// if(res == 0) handle timeout, res < 0 handle error

epoll不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。

int res = epoll_wait(epfd, events, 20, 120);

for (int i = 0; i < res;i++)

{

handleEvent(events[n]);

}

l  epoll关键数据结构

前面提到epoll速度快和其数据结构密不可分,其关键数据结构就是:

struct  epoll_event {

__uint32_t      events;  //epoll events

epoll_data_t   data;   //user data variable

};

typedef  union epoll_data {

void*   ptr;

int      fd;

__uint32_t  u32;

__uint64_t  u64;

}epoll_data_t;

可见epoll_data是一个union结构体,借助于它应用程序可以保存很多类型的信息:fd、指针等等。有了它,应用程序就可以直接定位目标了。

l  使用epoll

epollAPI:

#include  <sys/epoll.h>

int  epoll_create(int  size);

int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

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

  • int  epoll_create(int size);

创建一个epoll的文件描述符,参数size告诉内核这个监听的数目共有多大。

  • int  epoll_ctl(int epfd, int op, int fd, structepoll_event *event);

epoll的事件注册函数。

参数epfd是epoll_create返回值。

参数op为

EPOLL_CTL_ADD 注册新的fd到epfd中

EPOLL_CTL_MOD 修改已经注册的fd的监听事件

EPOLL_CTL_DEL 从epfd中删除一个fd

参数fd是需要监听文件描述符。

参数event是告诉内核需要监听什么事件。event->events的不同的值表示对应的文件描述符的不同事件:

EPOLLIN  可以读(包括对端Socket正常关闭)

EPOLLOUT 可以写

EPOLLPRI有紧急的数据可读(有带外数据OOB到来,TCP中的URG包)

EPOLLERR该文件描述符发生错误

EPOLLHUP该文件描述符被挂断

EPOLLET     将epoll设置为边缘触发(Edge Triggered)模式。

EPOLLONESHOT只监听一次事件,监听完之后,如果还想监听需要再次把该文件描述符加入到epoll队列中

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

等待事件的产生。

参数events用来从内核得到事件的集合

参数maxevents告之内核这个events有多大(maxevents不能大于size)

参数timeout是超时时间(毫秒)

epoll的模式:

  • LT模式:Level Triggered水平触发,

这个是缺省的工作模式。同时支持block socket和non-block socket。内核会告诉程序员一个文件描述符是否就绪了。如果程序员不作任何操作,内核仍会通知。

  • ET模式:Edge Triggered 边缘触发

是一种高速模式。仅当状态发生变化的时候才获得通知。这种模式假定程序员在收到一次通知后能够完整地处理事件,于是内核不再通知这一事件。注意:缓冲区中还有未处理的数据不算状态变化,所以ET模式下程序员只读取了一部分数据就再也得不到通知了,正确的用法是程序员自己确认读完了所有的字节(一直调用read/write直到出错EAGAIN为止)。

一个例子:

#include <netdb.h>

#include <sys/socket.h>

#include <sys/epoll.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <string.h>

#include <stdlib.h>

#include <errno.h>

/*创建并绑定一个socket作为服务器。 */

staticint  create_and_bind (char *port){

struct  addrinfo hints;

struct  addrinfo *result, *rp;

int  s, sfd;

memset (&hints, 0, sizeof (struct addrinfo));

hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */

hints.ai_socktype = SOCK_STREAM; /* 设置为STREAM模式,即TCP链接 */

hints.ai_flags = AI_PASSIVE;     /* All interfaces */

s = getaddrinfo (NULL, port, &hints, &result);//获得本地主机的地址

if (s != 0){

fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));

return -1;

}

for (rp = result; rp != NULL; rp = rp->ai_next){//本地主机地址可能有多个,任意绑定一个即可

sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); //创建socket

if (sfd == -1)

continue;

s = bind (sfd, rp->ai_addr, rp->ai_addrlen); //并绑定socket

if (s == 0)

{

/* 绑定成功 */

break;

}

close (sfd);

}

if (rp == NULL){

fprintf (stderr, "Could not bind\n");

return -1;

}

freeaddrinfo (result);

return sfd;

}

/*

设置socket为非阻塞模式。

先get flag,或上O_NONBLOCK 再set flag。

*/

static  int   make_socket_non_blocking (int sfd) {

int flags, s;

flags = fcntl (sfd, F_GETFL, 0);

if (flags == -1){

perror ("fcntl");

return -1;

}

flags |= O_NONBLOCK;

s = fcntl (sfd, F_SETFL, flags);

if (s == -1){

perror ("fcntl");

return -1;

}

return 0;

}

#define  MAXEVENTS 64

/*

用法: ./epoll_test 8080

*/

int  main (int argc, char *argv[]) {

int sfd, s;

int efd;

struct  epoll_event event;

struct  epoll_event *events;

if (argc != 2) {

fprintf (stderr, "Usage: %s [port]\n", argv[0]);

exit (EXIT_FAILURE);

}

sfd = create_and_bind (argv[1]); //sfd为绑定后等待连接接入的文件描述符

s = make_socket_non_blocking (sfd);

s = listen (sfd, SOMAXCONN);

efd = epoll_create1 (0);

event.data.fd = sfd;

event.events = EPOLLIN | EPOLLET;

s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);

/* Buffer where events are returned,为events数组分配内存 */

events = (struct  epoll_event*)calloc (MAXEVENTS, sizeof event);

/* The event loop 事件循环*/

while (1) {

int n, i;

n = epoll_wait (efd, events, MAXEVENTS, -1);

for (i = 0; i < n; i++) {

if ((events[i].events & EPOLLERR) ||  (events[i].events & EPOLLHUP) || (!(events[i].events & EPOLLIN))) {

/* 发生了错误或者被挂断,或者没有数据可读  An error has occured on this fd, or the socket is not ready for reading (why were we notified then?) */

fprintf (stderr, "epoll error\n");

close (events[i].data.fd);

continue;

}elseif (sfd == events[i].data.fd) {//新连接

/* sfd上有数据可读,则表示有新连接

* We have a notification on the listening socket,

* which means one or more incoming connections. */

printf("Incoming connection !\n");

while (1) {

struct sockaddr in_addr;

socklen_t in_len;

int infd;

char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];

in_len = sizeof in_addr;

infd = accept (sfd, &in_addr, &in_len); //读取到来的连接socket fd。

if (infd == -1) {

if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) {

/* 已经读完了sfd上的所有数据(所有连接)。最后一次读(非阻塞读)会返回EAGAIN(=EWOULDBLOCK)

* We have processed all incoming connections. */

break;

else  {

perror ("accept");

break;

}

}

s = getnameinfo (&in_addr, in_len, hbuf, sizeof hbuf, sbuf, sizeof sbuf, NI_NUMERICHOST | NI_NUMERICSERV);

if (s == 0) {

printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", infd, hbuf, sbuf);

}

s = make_socket_non_blocking (infd);  //设置socket为非阻塞模式

event.data.fd = infd;  //将data部分设置为fd

event.events = EPOLLIN | EPOLLET;  //监听EPOLLIN事件,使用边缘触发模式

s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);

}

continue;

else {//有客户端发来数据

/* 有客户端发来数据,因为处于ET模式,所以必须完全读取所有数据(要不然,剩下一部分数据后,就无法再收到内核通知了)。*/

int done = 0;

while (1) {

ssize_t count;

char buf[512];

count = read (events[i].data.fd, buf, sizeof buf);

if (count == -1) {

if (errno != EAGAIN) { //如果errno=EAGAIN,表示我们已经读取了所有的数据

perror ("read");

done = 1;

}

break;

elseif (count == 0) {  //读到文件尾(对端被关闭了)

/* End of file. The remote has closed the connection. */

done = 1;

break;

}

s = write (1, buf, count); /* 打印到屏幕 */

}

if (done) { //读完关闭(假设应用对每个连接只读取一次数据)

printf ("Closed connection on descriptor %d\n", events[i].data.fd);

/* Closing the descriptor will make epoll remove it from the set of descriptors which are monitored. */

close (events[i].data.fd);

}

}

}

}

free (events);//释放内存

close (sfd);   //关闭sfd

return EXIT_SUCCESS;

}

主要参考链接:http://blog.csdn.net/ljx0305/article/details/4065058

最新文章

  1. Subsets
  2. eclipse或者myeclipse安装svn报错”unable to load default svn client”
  3. SharePoint基于windows验证的如何通过组策略实现IE自动以当前域账号登录SP站点
  4. httpd服务访问控制
  5. ICP 算法步骤
  6. Ubuntu 安装 Xfce4 桌面
  7. python中列表和元组以及字符串的操作
  8. struts文件上传(多文件)
  9. Disney English
  10. Linux coredump
  11. 【转】android出现注: 某些输入文件使用或覆盖了已过时的 API。 注: 有关详细信息, 请使用 -Xlint:deprecation 重新编译。 注: 某些输入文件使用了未经检查或不安全的操作。 注
  12. 【原创】Linux编译内核
  13. int? 参数是这个的时候 是可以传入null的 而int的就不行
  14. 顺序栈和链式栈(C++实现)
  15. 重温C语言小感
  16. Unity UGUI
  17. access窗口标签居中
  18. 多线程学习系列二(使用System.Threading)
  19. ztree搜索节点并展开
  20. mysql 设置skip_name_resolve参数 日志 [Warning] &#39;user&#39; entry &#39;root@localhost&#39; ignored in --skip-name-resolve mode

热门文章

  1. jackson springboot null节点忽略配置
  2. java Cookie 获取历史记录列表(三)
  3. 快速切题 sgu136. Erasing Edges
  4. Jenkins无法读取覆盖率报告的解决方法
  5. java垃圾回收期如何工作(编程思想)
  6. python实现八大排序算法
  7. 树的直径证明+HDU2196
  8. 泰克 Tektronix THS720A 示波器 显示屏维修记录
  9. excel 条件格式 心的
  10. Ubuntu14.04下Sublime Text 3解决无法输入中文