多路复用 epoll

epoll特点

epoll 的主要特点包括:

1.没有文件描述符数量限制:与 select 和 poll 不同,epoll 采用了基于事件的就绪通知机制,没有预定义的文件描述符数量限制,可以支持更大规模的并发连接。

2.高效的事件通知:epoll 使用了内核和用户空间共享的事件数据结构,将文件描述符的事件注册到内核空间,当事件就绪时,内核直接将就绪的事件通知给用户空间,避免了每次调用都需要遍历整个文件描述符数组的性能开销。

3.分离的就绪事件集合:epoll 将就绪的事件从内核空间复制到用户空间,形成一个分离的就绪事件集合,用户可以直接遍历这个集合来处理就绪的事件,而不需要遍历整个文件描述符数组。

4.支持边缘触发和水平触发:epoll 提供了两种模式来处理事件,一种是边缘触发模式(EPOLLET),只在状态发生变化时通知应用程序,另一种是水平触发模式(默认),在事件就绪期间一直通知应用程序。

5.更低的内存拷贝开销:epoll 使用内存映射技术,避免了每次调用都需要将事件数据从内核复制到用户空间的开销,从而减少了系统调用的次数和内存拷贝的开销。

函数说明

epoll_create

创建epoll实例,后续epoll操作关联此epoll实例(close(2), epoll_ctl(2), epoll_wait(2))

#include <sys/epoll.h>

/****************************************************************************
函 数 名  : epoll_create
功能描述  : 创建epoll实例
输入参数  : int size > 0 Linux 2.6.8后此值无用
输出参数  : 
返 回 值  : int -1 失败,并设置错误码 非0 epoll实例文件描述符

**************************************************************************/
int epoll_create(int size);

epoll_ctl

操作选型

  • EPOLL_CTL_ADD  添加
  • EPOLL_CTL_MOD 修改
  • EPOLL_CTL_DEL  删除

           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 */
           };

监听的事件

  • EPOLLIN                0x01 可读
  • EPOLLOUT            0x04 可写 tcp连上触发一次,套接字可写触发
  • EPOLLRDHUP       0x2000 对端关闭连接或kill
  • EPOLLPRI              0x02    epoll文件描述符异常
  • EPOLLERR             0x08    关联文件描述符上发生错误情况,没必要设置因为出现会报
  • EPOLLHUP             0x10    在关联的文件描述符上发生挂起,   没必要设置因为出现会报
  • EPOLLET                0x80000000 边缘触发
  • EPOLLONESHOT   0x40000000
  • EPOLLWAKEUP      0x20000000 
  • EPOLLEXCLUSIVE 0x10000000
#include <sys/epoll.h>

/****************************************************************************
函 数 名  : epoll_ctl
功能描述  : epoll实例操作
输入参数  : int epfd epoll实例描述符
           int op   操作选项
           int fd   描述符
           struct epoll_event *event 关联描述符的事件对象
输出参数  : 
返 回 值  :int -1 失败 并设置错误码 0成功
**************************************************************************/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll_wait

阻塞等待关联描述符就绪,timeout-1 永久阻塞知道关联描述符就绪,timeout>0超时时间

描述符就绪时会将就绪描述符置于 events中,可遍历列表处理,很高效

#include <sys/epoll.h>

/****************************************************************************
 函 数 名  : epoll_wait
 功能描述  : epoll监听关联描述符事件
 输入参数  : int epfd    epoll实例描述符
            int maxevents 监听描述符最大数
            int timeout   超时时间 ms
 输出参数  : struct epoll_event *events 事件就绪列表
 返 回 值  :int -1 失败 0成功
 
**************************************************************************/
int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);

源码实例

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>

#define SERVER_PORT       9999
#define SERVER_LISTEN_NUM 10
#define TIMEOUT_SEC       5
#define TIMEOUT_USEC      0
#define SOKCETLIST_MAX    SERVER_LISTEN_NUM+1

/*
struct sockaddr {
    sa_family_t sa_family;
    char        sa_data[14];
}

struct timeval {
    long    tv_sec;         seconds 
    long    tv_usec;         microseconds
};

*/

typedef struct
{
    int use;
    int sock;
}SocketList_t;

SocketList_t g_sock_list[SOKCETLIST_MAX];

int socketlist_init(void)
{
    int i = 0;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        g_sock_list[i].use  = 0;
        g_sock_list[i].sock = -1;
    }

}

int socketlist_add(int sock)
{
    int i = 0;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (g_sock_list[i].use)
        {
            continue;
        }

        g_sock_list[i].use  = 1;
        g_sock_list[i].sock = sock;

        return 0;
    }

    return -1;
}

void socketlist_del(int sock)
{
    int i = 0;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (g_sock_list[i].use && g_sock_list[i].sock == sock)
        {
            g_sock_list[i].use  = 0;
            g_sock_list[i].sock = -1;
            return;
        }
    }
}

int socketlist_fdmax_get(void)
{
    int i      = 0;
    int maxfd = -1;

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (!g_sock_list[i].use)
        {
            continue;
        }

        maxfd = g_sock_list[i].sock >= maxfd ? g_sock_list[i].sock : maxfd;
    }

    return maxfd;
}

void sock_init_before_select(fd_set *readset, fd_set *writeset, fd_set *errorfds)
{
    int i = 0;

    if (readset)
    {
        FD_ZERO(readset);
    }
    if (writeset)
    {
        FD_ZERO(writeset);
    }
    if (errorfds)
    {
        FD_ZERO(errorfds);
    }

    for (i = 0; i < SOKCETLIST_MAX; i++)
    {
        if (!g_sock_list[i].use)
        {
            continue;
        }

        if (readset)
        {
            FD_SET(g_sock_list[i].sock, readset);
        }

        if (writeset)
        {
            FD_SET(g_sock_list[i].sock, writeset);
        }

        if (errorfds)
        {
            FD_SET(g_sock_list[i].sock, errorfds);
        }
    }
}

/***************************
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 位掩码,可多个事件(EPOLLIN EPOLLOUT EPOLLRDHUP
    EPOLLPRI EPOLLERR EPOLLHUP EPOLLET EPOLLONESHOT EPOLLWAKEUP EPOLLEXCLUSIVE)
    epoll_data_t data;        // User data variable
};
*******************************/

int main(void *arg, char *argv[])
{
    int i           = 0;
    int sock        = -1;
    int listen_sock = -1;
    int ret         = 0;
    int epoll_fd_num = 0;
    struct sockaddr_in address;
    struct sockaddr_in remote_addr;
    socklen_t remote_addrlen = sizeof(remote_addr);
	unsigned long optval = 1;
    unsigned char recv_buf[256];
    int epoll_instacne = 0;
    struct epoll_event event;
    struct epoll_event events[SOKCETLIST_MAX];

    printf("EPOLLIN:%x, EPOLLOUT:%x, EPOLLRDHUP:%x, EPOLLPRI:%x, EPOLLERR:%x, EPOLLHUP:%x, EPOLLET:%x, EPOLLONESHOT:%x,EPOLLWAKEUP:%x, EPOLLEXCLUSIVE:%x\r\n",
        EPOLLIN, EPOLLOUT, EPOLLRDHUP, EPOLLPRI, EPOLLERR, EPOLLHUP, EPOLLET, EPOLLONESHOT,EPOLLWAKEUP, EPOLLEXCLUSIVE);

    socketlist_init();

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == sock)
    {
        printf("socket fail\r\n");
        return -1;
    }

    //一般来说,一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    address.sin_family = AF_INET;
    address.sin_port = htons(SERVER_PORT);
    address.sin_addr.s_addr = INADDR_ANY;
    ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
    if (-1 == ret)
    {
        printf("bind fail\r\n");
        return -1;
    }

    ret = listen(sock, SERVER_LISTEN_NUM);
    if (-1 == ret)
    {
        printf("listen fail\r\n");
        return -1;
    }

    epoll_instacne = epoll_create(1);
    if (-1 == epoll_instacne)
    {
        printf("epoll_create fail %s\r\n", strerror(errno));
        return -1;
    }
    socketlist_add(sock);

    event.events = EPOLLIN;
    event.data.fd =sock;

    ret = epoll_ctl(epoll_instacne, EPOLL_CTL_ADD, sock, &event);
    while (1)
    {
        epoll_fd_num = epoll_wait(epoll_instacne, events, SOKCETLIST_MAX, -1);
        if (-1 == epoll_fd_num)
        {
            printf("epoll_wait fail %s\r\n", strerror(errno));
            return -1;
        }
        else if (0 == epoll_fd_num)
        {
            printf("epoll_wait timeout\r\n");
            continue;
        }

        for (i = 0; i < epoll_fd_num; i++)
        {
            if (events[i].data.fd == sock)
            {
                listen_sock = accept(sock, (struct sockaddr*)&remote_addr, &remote_addrlen);
                event.events = EPOLLIN | EPOLLRDHUP | EPOLLET | EPOLLOUT;
                event.data.fd =listen_sock;
                epoll_ctl(epoll_instacne, EPOLL_CTL_ADD, listen_sock, &event);
            }
            else
            {
                if (events[i].events & EPOLLRDHUP || events[i].events & EPOLLERR)      //对端关闭连接或异常
                {
                    socketlist_del(events[i].data.fd);
                    epoll_ctl(epoll_instacne, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);
                    printf("client close event:%x\r\n", events[i].events);
                    continue;
                }
                else if (events[i].events & EPOLLIN)
                {
                    memset(recv_buf, 0, sizeof(recv_buf));
                    ret = recv(events[i].data.fd, recv_buf, sizeof(recv_buf), 0);
                    if (-1 == ret)
                    {
                        printf("recv fail:%s, %d", strerror(errno), events[i].events);
                        socketlist_del(events[i].data.fd);
                        epoll_ctl(epoll_instacne, EPOLL_CTL_DEL, events[i].data.fd, &events[i]);
                        continue;
                    }
                    printf("recv ret:%d, data:%s\r\n", ret, recv_buf);
                }
            }
        }
    }

}

  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值