网络编程-多路转接之poll与epoll模型

首先,还是需要理解io过程:io过程总体来看分两步,第一步就是等,第二步才是数据搬迁。而如果要想提高io的性能与效率,就要减少等的比重。
可以假想一个场景:
你去钓鱼,但是你只有一个鱼竿。你的同伴也和你一起去钓鱼,但是他带了100个鱼竿。假设每条鱼上钩的概率都是一样的,那么你和他相同的时间内,你在死盯着一个鱼竿,而他只需要来回巡视所有的鱼竿,一旦有鱼上钩,拿上来即可。很明显,它的这种方式就要比你高效得多。

如果理想情况下它的鱼钩足够多,就会出现一种情况,每秒内都有鱼上钩。
替换到我们的io模型中,多路转接就能够实现这种近似理想化的情况。

但是最重要的一点:
不论是之前的select还是poll与epoll模型,本质上都是为了io过程中的等待这一过程。

poll

poll在用法上与select大致相同。

 #include <poll.h>

 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
 //pollfd结构
 struct pollfd
  {
    int fd;         /* File descriptor to poll.  */
    short int events;       /* Types of events poller cares about.  */
    short int revents;      /* Types of events that actually occurred.  */
  };

参数说明:

  • fds是一个poll函数监听的结构列表,每一个元素中,包含了三个部分:文件描述符,监听的事件集合,返回的事件集合。
  • nfds:表示fds数组的长度。
  • timeout:表示函数的超时时间,单位是毫秒

events的取值:
revents的取值:

返回值;

  • 小于0:表示出错
  • 等于0:表示poll函数等待出错
  • 大于0:表示poll函数由由于监听的文件描述符就绪而返回

优点

select使用三个位图来表示关心的事件类型,而poll使用了一个结构体指针实现。
并且使用位图,对于关心的文件描述符上限也受制于位图的大小。

  • pollfd结构包含了要关心的文件描述符和关心的事件以及发生的事件,比select使用起来更方便
  • poll对文件描述符没有数量的限制。

缺点

  • 和select函数一样,poll返回后,需要使用pollfd轮询来获取就绪的文件描述符。
  • 每次调用poll都需要把大量的pollfd结构从用户态拷贝到内存中。
  • 随着文件描述符上升,效率也会逐渐下降。

    代码实现:

#define MAX 1024
typedef struct pollfd pollfd;
void Add(int fd,pollfd* fd_list,int size)
{
    int i=0;
    for(i=0;i<size;i++)
    {
        if(fd_list[i].fd==-1)
        {
            fd_list[i].fd=fd;
            fd_list[i].events=POLLIN;
            break;
        }
    }
}
void Init(pollfd* fd_list,int size)
{
    int i=0;
    for(i=0;i<size;i++)
    {
        fd_list[i].fd=-1;
        fd_list[i].events=0;
        fd_list[i].revents=0;
    }
}
int startup(int port)
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_addr.s_addr=htonl(INADDR_ANY);
    local.sin_port=htons(port);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        exit(2);
    }
    if(listen(sock,5)<0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        printf("Usage:[%s port]\n",argv[0]);
        return 1;
    }

    int new_sock=startup(atoi(argv[1]));
    pollfd fd_list[MAX];
    Init(fd_list,sizeof(fd_list)/sizeof(pollfd));//???
    Add(new_sock,fd_list,sizeof(fd_list)/sizeof(pollfd));

    for(;;)
    {
        int ret=poll(fd_list,sizeof(fd_list)/sizeof(pollfd),1000);
        if(ret<0)
        {
            perror("poll");
            continue;
        }
        else if(ret==0)
        {
            printf("timeout...\n");
            continue;
        }
        size_t i=0;
        for(i=0;i<sizeof(fd_list)/sizeof(pollfd);i++)
        {
            if(fd_list[i].fd==-1)
            {
                continue;
            }
            if(!(fd_list[i].revents & POLLIN))
            {
                continue;
            }
            if(fd_list[i].fd==new_sock)
            {
                struct sockaddr_in client;
                socklen_t len=sizeof(client);
                int connect_sock=accept(new_sock,(struct sockaddr*)&client,&len);
                if(connect_sock<0)
                {
                    perror("accpet");
                    continue;
                }
                Add(connect_sock,fd_list,sizeof(fd_list)/sizeof(pollfd));
            }
            else
            {
                char buf[MAX];
                ssize_t s=read(fd_list[i].fd,buf,sizeof(buf)-1);
                if(s<0)
                {
                    perror("read");
                    continue;
                }
                else if(s==0)
                {
                    printf("client quit!\n");
                    close(fd_list[i].fd);
                    fd_list[i].fd=-1;
                }
                else
                {
                    printf("client say# %s\n",buf);
                    //write(fd_list[i].fd,buf,strlen(buf));
                }
            }
        }

    }
}

epoll模型

epoll,从命名上就可以看出与poll应该是有关联的。按照man手册的说法:epoll是为处理大批量句柄而做了改进的poll。他几乎具备了之前的poll与select的所有优点。

相关系统调用

epoll_create

调用该函数,操作系统会帮我们做三件事。

  1. 创建一颗红黑二叉树
  2. 创建一个就绪队列
  3. 创建回调机制

以上称作创建一个epoll模型。

   #include <sys/epoll.h>
   int epoll_create(int size);

其中参数size是被忽略的(在Linux2.6.8以后),虽然是被忽略的,为了防止跨版本的问题,所以尽可能地给一个较大的值。
该函数用完之后必须用close函数关闭。

epoll_ctl

   #include <sys/epoll.h>
   int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

该函数是epoll的注册事件函数:

  • 第一个参数是epoll_create的返回值
  • 第二个参数表示动作,用三个宏表示
  • 宏的取值:
    • EPOLL_CTL_ADD:注册新的fd至epfd中
    • EPOLL_CTL_MOD :修改已经注册的fd的监听事件
    • EPOLL_CTL_DEL:从epfd中删除一个fd
  • 第三个参数是需要监听的fd
  • 第四个参数是告诉内核需要监听什么事。
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 events */
  epoll_data_t data;    /* User data variable */
} __EPOLL_PACKED;

event的取值:

enum EPOLL_EVENTS
  {
    EPOLLIN = 0x001,//关心读事件
#define EPOLLIN EPOLLIN
    EPOLLPRI = 0x002,
#define EPOLLPRI EPOLLPRI
    EPOLLOUT = 0x004,
#define EPOLLOUT EPOLLOUT
    EPOLLRDNORM = 0x040,
#define EPOLLRDNORM EPOLLRDNORM
    EPOLLRDBAND = 0x080,
#define EPOLLRDBAND EPOLLRDBAND
    EPOLLWRNORM = 0x100,
#define EPOLLWRNORM EPOLLWRNORM
    EPOLLWRBAND = 0x200,
#define EPOLLWRBAND EPOLLWRBAND
    EPOLLMSG = 0x400,
#define EPOLLMSG EPOLLMSG
    EPOLLERR = 0x008,
#define EPOLLERR EPOLLERR
    EPOLLHUP = 0x010,
#define EPOLLHUP EPOLLHUP
    EPOLLRDHUP = 0x2000,
#define EPOLLRDHUP EPOLLRDHUP
    EPOLLWAKEUP = 1u << 29,
#define EPOLLWAKEUP EPOLLWAKEUP
    EPOLLONESHOT = 1u << 30,
#define EPOLLONESHOT EPOLLONESHOT
    EPOLLET = 1u << 31
#define EPOLLET EPOLLET
  };

可以看出这些宏是一个个的宏,并且是一个二进制序列且只有一个1不会重复。所以需要关心多个使用多个按位或即可。

epoll_wait

   #include <sys/epoll.h>
    int epoll_wait(int epfd, struct epoll_event *events,
                  int maxevents, int timeout);

该函数收集在epoll监控的事件中已经发送的事件。
- 参数events事分配好的epoll_event结构体数组
- epoll将会把发生的事件赋值到events数组中
- maxevents告诉内核这个events有多大,不能大于创建epoll模型时的size大小
- 参数timeout是超时时间
- 如果函数调用成功,返回对应i/o上已准备好的文件描述符数目,返回0表示已超时,小于0表示失败。

工作原理

当调用epoll_create函数时,系统会创建一个epoll模型,也就是做三件事,创建红黑树,就绪的队列,回调机制。

红黑树将存储epoll所监听的套接字。用来存储所有的套接字,当进行add或者del的时候,都从红黑树上去处理,这样时间复杂度就可以保持在O(logn)。

当添加事件以后,这个事件就会和相应的设备驱动程序建立回调关系,当相应的时间发生的时候,这个时候就会去调用回调函数。回调函数就完成了把时间添加到链表当中。

当我们执行epoll_ctl时,除了把socket放到epoll文件系统里file对象对应的红黑树上之外,还会给内核中断处理程序注册一个回调函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备就绪链表里了。

总结下来:epoll的使用方法分三步:

  1. 创建epoll模型
  2. 进行注册关心的文件描述符
  3. 等待文件描述符就绪

epoll的优点

其实从epoll的工作原理就能够看出epoll虽然原理复杂,但是使用起来会比select方便。不管是poll还是select数据结构都需要自己去维护,而epoll不需要,只需要你无脑式地将你关心地文件描述符事件注册进去,并且直接去就绪队列中取即可。

优点总结:
- 文件描述符无上限:通过epoll_ctl来注册一个文件描述符,内核中使用红黑树来管理所有需要监控地文件描述符。
- 基于事件的就绪通知方式:一旦被监听的某个文件描述符就绪,内核会采用回调机制,迅速激活该文件描述符,即是就绪的文件描述符增多,也不会影响性能。
- 维护就绪队列:当文件描述符就绪,就会被放到内核中的一个就绪队列中,只需要取队列中元素即可。

void service(int epfd,struct epoll_event *revs,int num,int listen_sock)
{
    //既关心读有关心写
    int  i=0;
    struct epoll_event ev;
    for(i=0;i<num;i++)
    {
        int fd=revs[i].data.fd;
        if(revs[i].events&EPOLLIN)
        {
            //read
            if(fd==listen_sock)//accept
            {
                struct sockaddr_in client;
                socklen_t len=sizeof(client);
                int new_fd=accept(fd,(struct sockaddr*)&client,&len);
                if(new_fd<0)
                {
                    perror("accept");
                    continue;
                }

                printf("get a connection!\n");

                ev.events=EPOLLIN;
                ev.data.fd=new_fd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&ev);
            }
            else
            {
                //route
                char buf[1024];
                ssize_t s=read(fd,buf,sizeof(buf));
                if(s>0)
                {
                    buf[s]=0;
                    printf("client:#%s\n",buf);
                    ev.events=EPOLLOUT;
                    ev.data.fd=fd;
                    epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);
                }
                else if(s==0)
                {
                    printf("client quit!\n");
                    close(fd);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);//删除该fd
                }
                else
                {
                    perror("read");
                    close(fd);
                    epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);//删除该fd
                }
            }
        }
        if(revs[i].events&EPOLLOUT)
        {
            //write
            const char* msg="HTTP/1.0 200 OK\r\n\r\n<html><h1>EPOLL SUCCESS:)</h1><></html>\r\n";
            write(fd,msg,strlen(msg));
            close(fd);
            epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
        }
    }
}
int startup(int port)
{
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_addr.s_addr=htonl(INADDR_ANY);
    local.sin_port=htons(port);

    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        perror("bind");
        exit(2);
    }
    if(listen(sock,5)<0)
    {
        perror("listen");
        exit(3);
    }
    return sock;
}
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        printf("Usage:[%s port]\n",argv[0]);
        return 1;
    }
    int listen_sock=startup(atoi(argv[1]));
    int epfd=epoll_create(MAX);
    if(epfd<0)
    {
        perror("epoll_create");
        return 5;
    }
    struct epoll_event ev;
    ev.events=EPOLLIN;
    ev.data.fd=listen_sock;
    epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);
    struct epoll_event r_ev_s[MAX];
    int size=0;
    for(;;)
    {
        switch(size=epoll_wait(epfd,r_ev_s,MAX,-1))
        {
            case -1:
                perror("epoll_wait");
                break;
            case 0:
                printf("timeout....\n");
                break;
            default:
                service(epfd,r_ev_s,size,listen_sock);
                break;
        }
    }
}

猜你喜欢

转载自blog.csdn.net/mxrrr_sunshine/article/details/80611856