简述epoll并发网络编程方法


select&epoll比较

select方法的缺点

1. 最大并发数限制,因为一个进程所打开的FD(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此Select模型的最大并发数就被相应限制了。自己改改这个FD_SETSIZE?想法虽好,可是先看看下面吧…

2. 效率问题,select每次调用都会线性扫描全部的FD集合,这样效率就会呈现线性下降,把FD_SETSIZE改大的后果就是,大家都慢慢来,什么?都超时了??!!

3. 内核/用户空间 内存拷贝问题,如何让内核把FD消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

epoll方法的优点

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

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

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

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

epoll的API

epoll相比select好用,基本API只有三个函数。

1. int epoll_create(int size)

生成一个epoll专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。

size是一个可以忽略的参数;

需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

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

控制某个epoll文件描述符上的事件:注册、修改、删除。相对于select模型中的FD_SET和FD_CLR宏。

epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值;

第二个参数表示动作,用三个宏来表示:

EPOLL_CTL_ADD:注册新的fd到epfd中;

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

EPOLL_CTL_DEL:从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

struct epoll_event {

   __uint32_t events;      // Epollevents

   epoll_data_t data;      // Userdatavariable

};

events可以是以下几个宏的集合:

EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP:表示对应的文件描述符被挂断;

EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

 

typedef union epoll_data {

   void *ptr;

    int fd;

   __uint32_t u32;

   __uint64_t u64;

} epoll_data_t;

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

3. int epoll_wait(int epfd,structepoll_event * events,int maxevents,int timeout)

等待I/O事件的发生,相对于select模型中的select函数,返回发生事件数。如返回0表示已超时。参数说明:

epfd:由epoll_create() 生成的epoll专用的文件描述符;

epoll_event:用于回传代处理事件的数组;从内核得到事件的集合;

maxevents:每次能处理的事件数;告之内核这个events有多大,这个 maxevents的值必须大于0;

timeout:等待I/O事件发生的超时值;(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞;

 

关于ET、LT两种工作模式

可以得出这样的结论:

ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;

而LT模式是只要有数据没有处理就会一直通知下去的.

epoll基本用法

首先通过epoll_create(intmaxfds)来创建一个epoll的句柄epoll_fd。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作,在用完之后,记得用close()来关闭这个创建出来的epoll句柄;

之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, inttimeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。

基本的语法为:

nfds = epoll_wait(epoll_fd, events, maxevents, -1);epoll_wait返回之后应该是一个循环,遍利所有的事件。

其中epoll_fd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件返回,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。

几乎所有的epoll程序都使用下面的框架:

   for( ; ; )

    {

       nfds = epoll_wait(epfd,events,20,500);

       for(i=0;i<nfds;++i)

       {

            if(events[i].data.fd==listenfd) //有新的连接

           {

                connfd =accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接

                ev.data.fd=connfd;

                ev.events=EPOLLIN|EPOLLET;

                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);//将新的fd添加到epoll的监听队列中

           }

           else if( events[i].events&EPOLLIN ) //接收到数据,读socket

           {

                n = read(sockfd, line,MAXLINE)) < 0    //读

                ev.data.ptr = md;     //md为自定义类型,添加数据

                ev.events=EPOLLOUT|EPOLLET;

               epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓

           }

           else if(events[i].events&EPOLLOUT) //有数据待发送,写socket

           {

                struct myepoll_data* md =(myepoll_data*)events[i].data.ptr;    //取数据

                sockfd = md->fd;

                send( sockfd, md->ptr,strlen((char*)md->ptr), 0 );        //发送数据

                ev.data.fd=sockfd;

                ev.events=EPOLLIN|EPOLLET;

               epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据

           }

           else

           {

                //其他的处理

           }

       }

    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值