Server Develop (六) Linux epoll总结

  epoll是Kernel 2.6后新加入的事件机制,在高并发条件下,远优于select。epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明: 

#define __FD_SETSIZE    1024 //select最多同时监听1024个fd

  当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。

  所以在Nginx中采用了epoll来实现其高并发特性。 

工作方式

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

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

  区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。

主要的数据结构

   epoll_event的结构如下:

typedef union epoll_data {
        void *ptr;
         int fd;
         __uint32_t u32;
         __uint64_t u64;
} epoll_data_t;//保存触发事件的某个文件描述符相关的数据

struct epoll_event {
         __uint32_t events;      /* epoll event */
         epoll_data_t data;      /* User data variable */
};

  events表示感兴趣的事件和被触发的事件,可能的取值为:

EPOLLIN 对应的文件描述符可以读
EPOLLOUT 对应的文件描述符可以写
EPOLLPRI 对应的文件描述符有紧急的数可读
EPOLLERR 对应的文件描述符发生错误
EPOLLHUP 对应的文件描述符被挂断
EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里 

 操作函数

    epoll的接口非常简单,用三个相关函数来创建epoll句柄、注册epoll事件以及等待事件的发生。

  创建epoll句柄:

int epoll_create(int size);
//size表示内核需要监听的数目//return : epoll文件描述符

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

   epoll事件注册函数:

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

//epfd是epoll_create()的返回值
//op表示动作
/* op可被表示为:
    EPOLL_CTL_ADD:注册新的fd到epfd中;
    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
    EPOLL_CTL_DEL:从epfd中删除一个fd;
*/
//fd是需要监听的fd
//event是内核需要监听的事件

  等待事件发生函数:

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
//epfd是函数返回值
//events是内核监听事件的集合
//maxevents是epoll_wait可以处理的连接事件的最大限度值
//timeout是超时时间

//返回值:请求数

epoll工作流程

   首先,需要调用epoll_create创建epoll,此后我们就可以进行socket/bind/listen,然后调用epoll_ctl进行注册。接下来,就可以通过一个while(1)循环调用epoll_wait来等待事件的发生,然后循环查看接收到的事件并进行处理。如果事件是sever的socketfd我们就要进行accept,并且把接收到client的socketfd加入到要监听的事件中。如果在监听过程中,需要修改操作方式(读/写),可以调用epoll_ctl来重新修改。如果监听到某一个客户端关闭,那么我就需要再次调用epoll_ctl把它从epoll监听事件中删除。

 

 实例

#include <stdio.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>

void setnonblocking(int sockfd)
{
    int opts;
    opts = fcntl(sockfd, F_GETFL);
    if(opts < 0){
        perror("fcntl1 error!\n");
        exit(1);
    }
    opts = opts | O_NONBLOCK;
    if(fcntl(sockfd, F_SETFL, opts) < 0){
        perror("fcntl2 error!\n");
        exit(1);
    }
}

int main()
{
    int fd;
    int on;
    int rs;
    int len;
    int conn;
    char buffer[100];
    int flag1, flag2;
    struct sockaddr_in serv_addr, clt_addr;
    struct timeval timeout;

    int i;
    int nfds;
    int epfd;
    int newfd;

    struct epoll_event ev;
    struct epoll_event events[20];

    fd = socket(AF_INET, SOCK_STREAM, 0);

    on = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    timeout.tv_sec  = 5;
    timeout.tv_usec = 0;
    setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));

    bzero(&serv_addr, sizeof(struct sockaddr_in));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port   = htons(9090);
    serv_addr.sin_addr.s_addr = INADDR_ANY;

    rs = bind(fd, (struct sockaddr*)(&serv_addr), sizeof(struct sockaddr));
    if(rs < 0){
        perror("");
        close(fd);
        return -1;
    }

    setnonblocking(fd);

    epfd = epoll_create(100);

    ev.data.fd = fd;
    ev.events = EPOLLIN|EPOLLET;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

    rs = listen(fd, 5);
    if(rs < 0){
        perror("");
        close(fd);
        return -1;
    }

    len = sizeof(struct sockaddr);

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

        for(i = 0; i < nfds; ++i){
            if(events[i].data.fd == fd){
                conn = accept(fd, (struct sockaddr*)(&clt_addr), (unsigned int*)(&len));

                setnonblocking(conn);

                ev.data.fd = conn;
                ev.events = EPOLLIN|EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, conn, &ev);
            }
            else if(events[i].events & EPOLLIN){
                if((newfd = events[i].data.fd) < 0)
                    continue;
                bzero(buffer, sizeof(buffer));
                flag1 = recv(newfd, buffer, 100, 0);
                printf("recv: %s\n", buffer);
                printf("recv return: %d\n", flag1);

                ev.data.fd = newfd;
                ev.events = EPOLLOUT|EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev);
            }
            else if(events[i].events & EPOLLOUT){
                if((newfd = events[i].data.fd) < 0)
                    continue;

                bzero(buffer, sizeof(buffer));
                strcpy(buffer, "ACK");
                flag2 = send(newfd, buffer, sizeof(buffer), 0);
                printf("recv return: %d\n\n", flag2);    

                ev.data.fd = newfd;
                ev.events = EPOLLIN|EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev);
            }
        }
    }
    close(fd);

    return 0;
}

参考

    

        http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html

     http://hi.baidu.com/jingweiyoung/item/ae9fc81714be67dbbf9042b9

     http://www.linuxidc.com/Linux/2011-04/35156p3.htm

   http://www.cppblog.com/converse/archive/2008/04/29/48482.html


本文 由 cococo点点 创作,采用 知识共享 署名-非商业性使用-相同方式共享 3.0 中国大陆 许可协议进行许可。欢迎转载,请注明出处:
转载自:cococo点点 http://www.cnblogs.com/coder2012

时间: 2016-05-20

Server Develop (六) Linux epoll总结的相关文章

Server Develop (七) Linux 守护进程

守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程常常在系统引导装入时启动,在系统关闭时终止.Linux系统有很多守护进程,大多数服务都是通过守护进程实现的. 守护进程的特点 由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭.但是守护进程却

Server Develop (四) select实现非阻塞sever

Select server linux 的socket函数分为阻塞和非阻塞两种方式,比如accept函数,在阻塞模式下,它会一直等待有客户连接.而在非阻塞情况下,会立刻返回.我们一般都希望程序能够运行在非阻塞模式下.一种方法就是做一个死循环,不断去查询各个socket的状态,但是这样会浪费大量的cpu时间.解决这个问题的一个方法就是使用select函数.使用select函数可以以非阻塞的方式和多个socket通信.当有socket需要处理时,select函数立刻返回,期间并不会占用cpu时间. 

Server Develop (五) Linux并发模型

目前可以实现并发程序的方法有Apache模型(Process Per Connection,简称PPC),TPC(Thread PerConnection)模型,以及select模型和poll模型.Epoll模型. 各种模型优缺点 Apache模型和TPC模型是最容易理解的,Apache模型在并发上是通过多进程实现的,而TPC模型是通过多线程实现的,但是这种方式在大量进程/线程切换时会造成大量的开销. select模型是通过一种轮询机制来实现的.需要注意: Socket数量限制:该模式可操作的S

Linux Epoll介绍和程序实例

1. Epoll 是何方神圣?Epoll 可是当前在 Linux 下开发大规模并发网络程序的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 I/O 多路复用技术而已 ,并没有什么神秘的. 其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 PPC ), TPC ( Thread Per Connection )模型,以及 select 模型和 poll 模型,

Server Develop (八) IOCP模型

IOCP全称I/O Completion Port,中文译为I/O完成端口.IOCP是一个异步I/O的Windows API,它可以高效地将I/O事件通知给应用程序,类似于Linux中的Epoll. 简介 IOCP模型属于一种通讯模型,适用于Windows平台下高负载服务器的一个技术.在处理大量用户并发请求时,如果采用一个用户一个线程的方式那将造成CPU在这成千上万的线程间进行切换,后果是不可想象的.而IOCP完成端口模型则完全不会如此处理,它的理论是并行的线程数量必须有一个上限-也就是说同时发

C++ linux epoll并发服务器模型初探

socket通讯流程图 最简单的可以通讯的C++服务器端代码: #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #define SERV_PORT 8000 int main(void) { int lfd, cfd;

linux epoll浅析

首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象.     不管是文件,还是套接字,还是管道,我们都可以把他们看作流.     之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据:通过write,我们可以往流写入数据.现在假定一个情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办? 阻塞:阻塞是个什么概念呢?比如某个时候你在等快递,但是你不知道

Server Develop (二) 多线程实现C/S

多线程实现服务器 //线程需要调用的函数 void pthread_work(struct argc *argc2) { int flag; struct argc argc1; argc1.sock_clt = argc2->sock_clt; argc1.sock_serv = argc2->sock_serv; printf("%d,%d\n",argc1.sock_clt,argc1.sock_serv); char buffer[1024]; strcpy(buf

重磅消息:SQL Server 开始支持 Linux ?

事实上,在微软的云服务 Azure 中,有将近四分之一的服务是由 Linux 操作系统提供.然而我们却不能使微软的软件运行在 Linux 服务器上.这真的是十分尴尬. 但就在本周,微软终于开始做出了改变.微软的相关负责人 Scott Guthrie 在博客中表示:很快我们就可以在 Linux 服务器上运行微软的 SQL Server 数据库软件.虽然只是 SQL Server ,但这至少是一个不错的开始. 不过微软也表示在 Linux 上很可能只能运行 SQL Server 的一部分组件.而其他