欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

epoll服务器小结

程序员文章站 2022-06-14 11:10:45
...

epoll简介:

  epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
  目前epell是linux大规模并发网络程序中的热门首选模型。
  epoll除了提供select/poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
可以使用cat命令查看一个进程可以打开的socket描述符上限。

cat /proc/sys/fs/file-max

如有需要,可以通过修改配置文件的方式修改该上限值。

sudo vi /etc/security/limits.conf
    在文件尾部写入以下配置,soft软限制,hard硬限制。如下图所示。
    * soft nofile 65536
    * hard nofile 100000

基础API

  • epoll_create

    创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。    
    
    #include <sys/epoll.h>
    
    int epoll_create(int size)      size:监听数目
  • epoll_ctl

    控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
        #include <sys/epoll.h>
        int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
        epfd:   为epoll_creat的句柄
        op:     表示动作,用3个宏来表示:
        EPOLL_CTL_ADD (注册新的fd到epfd),
        EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
        EPOLL_CTL_DEL (从epfd删除一个fd);
        event:  告诉内核需要监听的事件
    
        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;
    
        EPOLLIN :   表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
        EPOLLOUT:   表示对应的文件描述符可以写
        EPOLLPRI:   表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
        EPOLLERR:   表示对应的文件描述符发生错误
        EPOLLHUP:   表示对应的文件描述符被挂断;
        EPOLLET:    将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
        EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
  • epoll_wait

等待所监控文件描述符上有事件的产生,类似于select()调用。
    #include <sys/epoll.h>
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
    events:     用来存内核得到事件的集合,
    maxevents:  告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
    timeout:是超时时间
            -1: 阻塞
            0:  立即返回,非阻塞
            >0: 指定毫秒
    返回值:    成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1

样例:

为了使代码更加简洁,将常用的函数进行了二次封装,加入了出错处理,见wrap.c.

/*************************************************************************
    > File Name: server.c
    > Author: sunxingying
    > Mail: aaa@qq.com 
    > Created Time: 2017年06月29日 星期四 03时36分35秒
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/epoll.h>
#include"wrap.h"
#define _SIZE_ 128
#define OPEN_MAX 256
typedef struct epoll_msg
{
    int fd;
    char buf[_SIZE_];

}epoll_t,*epoll_p,**epoll_pp;
static void *allocator(int fd)
{
    epoll_p buf=(epoll_p)malloc(sizeof(epoll_t));
    if(NULL==buf)
    {
        perror("malloc");
        exit(6);
    }
    buf->fd=fd;
    return buf;
}
void delalloc(void *ptr)
{
    if(NULL!=ptr)
    {
        free(ptr);
    }
}
int startup(char *ip,int port)
{
    int sock=Socket(AF_INET,SOCK_STREAM,0);
    int opt=1;
    if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
    {
        perror("setsockopt");
        exit(1);
    }
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(port);
    local.sin_addr.s_addr=inet_addr(ip);
    Bind(sock,(struct sockaddr*)&local,sizeof(local));
    Listen(sock,10);
    return sock;
}
int main(int argc,char *argv[])
{
    if(argc!=3)
    {
        printf("请输入ip地址和端口号\n");
        return 1;
    }
    int lis_sock=startup(argv[1],atoi(argv[2]));
    //创建epoll模型,epfd指向红黑数跟节点
    int epfd=epoll_create(OPEN_MAX);
    if(epfd<0)
    {
        perror("epoll_create");
    }
    struct epoll_event envs;
    envs.events=EPOLLIN|EPOLLET;
    envs.data.ptr=allocator(lis_sock);
    //将lis_sock及对应的结构体设置到树上,epfd可找到该树
    epoll_ctl(epfd,EPOLL_CTL_ADD,lis_sock,&envs);
    while(1)
    {
        int num=0;
        int timeout=-1;
        struct epoll_event evs[32];
        int max=32;
        switch((num=epoll_wait(epfd,evs,max,timeout)))
        {
            case 0:
                printf("time out...");
                break;
            case -1:
                perror("epoll _wait");
                break;
            default:
                {
                    int i=0;
                    for(;i<num;i++)
                    {


                        int fd=((epoll_p)(evs[i].data.ptr))->fd;
                        if(fd==lis_sock&&evs[i].events&EPOLLIN)
                        {
                            struct sockaddr_in peer;
                            socklen_t len=sizeof(peer);
                            int connfd=Accept(lis_sock,(struct sockaddr*)&peer,&len);
                            envs.events=EPOLLIN;
                            envs.data.ptr=allocator(connfd);
                            epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&envs);
                        }
                        else if(fd!=lis_sock&&evs[i].events&EPOLLIN)
                        {
                            int s=read(fd,((epoll_p)(evs[i].data.ptr))->buf,_SIZE_-1);
                            if(s>0)
                            {
                                char *buf=((epoll_p)(evs[i].data.ptr))->buf;
                                buf[s]=0;
                                printf("client # %s\n",buf);
                                evs[i].events=EPOLLOUT;
                                epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&evs[i]);
                            }
                            else if(s==0)
                            {
                                epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
                                delalloc(evs[i].data.ptr);
                                close(fd);
                            }
                            else
                            {
                                perror("read");
                            }

                        }
                        else if(fd!=lis_sock&&evs[i].events&EPOLLOUT)
                        {
                            char *msg="http/1.0 200 ok\r\n\r\n<html><title>孙兴颖的epoll服务器</title><h1>欢迎来到孙兴颖的epoll服务器</h1><p>当前时间是:</p><script type=\"text/javascript\">document.write(Date())</script></html>";
                            write(fd,msg,strlen(msg));
                            delalloc(evs[i].data.ptr);
                            epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&evs[i]);
                            close(fd);
                        }

                    }
                }
        }
    }
    return 0;

}

wrap.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>

void perr_exit(const char *s)
{
    perror(s);
    exit(-1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;

again:
    //accept是慢速系统调用,有可能在阻塞期间被信号中断
    //被信号中断时errno 被设置为ECONNABORTED或者EINTR
    if ((n = accept(fd, sa, salenptr)) < 0) {
        if ((errno == ECONNABORTED) || (errno == EINTR))
            goto again;
        else
            perr_exit("accept error");
    }
    return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

    if ((n = bind(fd, sa, salen)) < 0)
        perr_exit("bind error");

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

    if ((n = connect(fd, sa, salen)) < 0)
        perr_exit("connect error");

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

    if ((n = listen(fd, backlog)) < 0)
        perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
    int n;

    if ((n = socket(family, type, protocol)) < 0)
        perr_exit("socket error");

    return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
    ssize_t n;
//read也是慢速系统调用
//read返回值:
/*
 *  1. >0实际读到的字节数 buf=1024 1.==buf 1024 2. <buf 56
 *  2. ==0读到了(文件、管道、socket末尾了--对端关闭
 *  3. -1 异常
 *          1. errno==EINTR 被信号中断  重启/quit
 *          2. error==EAGAIN(EWOULDBLOCK)非阻塞方式读,并且没有数据
 *          3. 其他值 出现错误 --perror exit
 *
 */
again:
    if ( (n = read(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
    ssize_t n;
//write也是慢速系统调用也会遇到同上问题,
again:
    if ( (n = write(fd, ptr, nbytes)) == -1) {
        if (errno == EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}

int Close(int fd)
{
    int n;
    if ((n = close(fd)) == -1)
        perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
    size_t  nleft;              //usigned int 剩余未读取的字节数
    ssize_t nread;              //int 实际读到的字节数
    char   *ptr;

    ptr = vptr;
    nleft = n;
    /*
     *假设socket有4096字节,而以太网帧格式最大一次是1500
     *一个数据包就发不完了,readn(cfd,buf,4096);
     *nleft是剩余还未读的字节数,nleft=4096-1500,
     *读完指针向后偏移
     */
    while (nleft > 0) {
        if ((nread = read(fd, ptr, nleft)) < 0) {
            if (errno == EINTR)
                nread = 0;
            else
                return -1;
        } else if (nread == 0)
            break;

        nleft -= nread;
        ptr += nread;
    }
    return n - nleft;
}
/*writen同readn*/
ssize_t Writen(int fd, const void *vptr, size_t n)
{
    size_t nleft;
    ssize_t nwritten;
    const char *ptr;

    ptr = vptr;
    nleft = n;
    while (nleft > 0) {
        if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
            if (nwritten < 0 && errno == EINTR)
                nwritten = 0;
            else
                return -1;
        }

        nleft -= nwritten;
        ptr += nwritten;
    }
    return n;
}

static ssize_t my_read(int fd, char *ptr)
{
    static int read_cnt;
    static char *read_ptr;
    static char read_buf[100];

    if (read_cnt <= 0) {
again:
        if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
            if (errno == EINTR)
                goto again;
            return -1;
        } else if (read_cnt == 0)
            return 0;
        read_ptr = read_buf;
    }
    read_cnt--;
    *ptr = *read_ptr++;

    return 1;
}
/*readline ---fgets*/
//传出参数vptr
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
    ssize_t n, rc;
    char    c, *ptr;
    ptr = vptr;
    for (n = 1; n < maxlen; n++) {
        if ( (rc = my_read(fd, &c)) == 1) {
            *ptr++ = c;
            if (c  == '\n')
                break;
        } else if (rc == 0) {
            *ptr = 0;
            return n - 1;
        } else
            return -1;
    }
    *ptr  = 0;

    return n;
}

wrap.h

#ifndef __WRAP_H_
#define __WRAP_H_

void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);

#endif

效果展示:

epoll服务器小结