Epoll 使用示例

By fireworks@foxmail.com


所谓边沿触发:将调用 epoll_wait 期间的事件进行了合并,因此事件数量较多时,边沿触发才显出优势

使用UDP时,如果 read 时缓冲区小于包长时,其结果是依赖于实现的,可能会导致epoll_wait出错

 

其他相关的背景知识,可以在网上轻松获取到,这里仅列一个示例的源码

------------------------------------------------------------------------------------------------------------------------------------------------

 

#if 0

作者:fireworks2@foxmail.com

说明:一个简易的 TCP/UDP - epoll 服务器使用样例,尽量简化了逻辑

日期:2011-04-11

#endif

 

#include <iostream>

#include <sys/socket.h>

#include <sys/epoll.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <fcntl.h>

#include <unistd.h>

#include <stdio.h>

#include <errno.h>

 

/* For KeepAlive */

#include <linux/tcp.h>

#include <linux/socket.h>

 

using namespace std;

 

#define DBG_TCP // 是否使用 TCP 样例,注释后为UDP

 

#define MAXSOCK 1000

#define MAXLINE 1024

#define LISTENQ 20

#define MAXEVENTS 30

#define TIMEOUT 500 // ms

 

#define LOCAL_IP "192.168.138.9"

#define LOCAL_PORT 5002

 

#define W_UDP SOCK_DGRAM

#define W_TCP SOCK_STREAM

 

#define ERRSTR strerror(errno) // 打印错误信息

 

/* 将一个FD设置为非阻塞 */

int w_UnblockFd(int fd)

{

    int opts;

 

    opts = fcntl(fd, F_GETFL);

    if (opts < 0)

    {

        return -1;

    }

 

    if (fcntl(fd, F_SETFL, opts)<0)

    {

        return -2;

    }   

}

 

/* 创建一个套接字 */

int w_Socket(int type){

    return socket(AF_INET, type, 0);

}

 

/* 开启 EPOLL */

int w_EpollCreate(int count){

    return epoll_create(count);

}

 

/* epoll等待事件发生 */

int w_EpollWait(int epfd, struct epoll_event *pEvents, int maxevents, int timeout){

    return epoll_wait(epfd, pEvents, maxevents, timeout); /* time out in ms */

}

 

/* 向epoll中加入一个描述符 */

int w_EpollAdd(int epfd, int sockfd, int events){

    struct epoll_event ev;

 

    ev.data.fd = sockfd;

    ev.events = events;

    return epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

}

 

/* 删除一个观察的描述符 */

int w_EpollDel(int epfd, int sockfd){

    int iRet = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);

    close(sockfd);

    return iRet;

}

 

/* 修改一个描述符的观测事件 */

int w_EpollMod(int epfd, int sockfd, int events){

    struct epoll_event ev;

 

    ev.data.fd = sockfd;

    ev.events = events;

    return epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

}

 

/* 查看事件是否为 IN */

bool w_IsPollIn(struct epoll_event *pEvent){

    if (NULL == pEvent){

        return false;

    }

    return pEvent->events & EPOLLIN;

}

/* 查看事件是否为 OUT */

bool w_IsPollOut(struct epoll_event *pEvent){

    if (NULL == pEvent){

        return false;

    }

    return pEvent->events & EPOLLOUT;

}

 

/* 从struct epoll_event中抽取fd */

int w_GetFdFromEvent(struct epoll_event *pEvent){

    return pEvent->data.fd;

}

 

/* 由struct sockaddr_in 得到IP地址、端口 */

int w_STAddr2IpPort(char** ppClientIp, int *pClientPort, struct sockaddr_in *pAddress){

    if (NULL == pAddress){

        return -1;

    }

 

    if (ppClientIp != NULL){

        *ppClientIp = inet_ntoa(pAddress->sin_addr);

    }    

    if (pClientPort != NULL){

        *pClientPort = ntohs(pAddress->sin_port);

    }

 

    return 0;

}

 

/* 由IP、端口得到struct sockaddr_in */

int w_IpPort2STAddr(struct sockaddr_in *pAddress, const char *pIp, int port){

    if (NULL == pAddress){

        return -1;

    }

 

    bzero(pAddress, sizeof(struct sockaddr));

    pAddress->sin_family = AF_INET;

    pAddress->sin_port = htons(port);

 

    return inet_aton(pIp, &(pAddress->sin_addr));

}

 

 

/* 设置套接字收/发超时, 同时设置收发(发送的也可以使用非阻塞) */

int w_SetSockTimeout(int sockfd, int ms){

    struct timeval stTimeval;

    stTimeval.tv_sec = ms / 1000;

    stTimeval.tv_usec = (ms % 1000) * 1000;

    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &stTimeval, sizeof(stTimeval));

    setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &stTimeval, sizeof(stTimeval));

}

 

/* 设置 KEEP_ALIVE 的存活探测 */

/* idleTime 秒没动静后,尝试以 intvlCheckTime 秒为间隔发探测包,最多探测 checkCnt 次 */

/* 探测失败会触发事件(epoll / select 感知为套接字可读) */

int w_SetKeepAlive(int sockfd, int idleTime, int intvlCheckTime, int checkCnt){

    int optval; 

    socklen_t optlen = sizeof(optval);    

 

    optval = 1;    

    if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen)){

        return -1;

    }

    optval = idleTime;

    if (setsockopt(sockfd, IPPROTO_TCP , TCP_KEEPIDLE, &optval, optlen)){

        return -2;

    }

    optval = intvlCheckTime;

    if (setsockopt(sockfd, IPPROTO_TCP , TCP_KEEPINTVL, &optval, optlen)){

        return -3;

    }

    optval = checkCnt;

    if (setsockopt(sockfd, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen)){

        return -4;

    }

 

    return 0;

}

 

 

/* 绑定一个地址:端口 */

int w_Bind(int sockfd, const char *pIp, int port){

    struct sockaddr_in addr;

 

#if 0

    bzero(&addr, sizeof(addr));

    addr.sin_family = AF_INET;

    inet_aton(ip, &(addr.sin_addr));

    addr.sin_port=htons(port);

#endif

    w_IpPort2STAddr(&addr, pIp, port);

 

    return bind(sockfd,(sockaddr *)&addr, sizeof(addr));

}

 

/* 接收一个套接字 */

int w_Accept(int sockfd, char** ppClientIp, int *pClientPort){

    struct sockaddr_in clientAddr;

    unsigned int addrlen = sizeof(clientAddr);

    int newSockfd = accept(sockfd, (sockaddr *)&clientAddr, &addrlen);

 

    if (newSockfd < 0){

        return newSockfd;

    }

 

    w_STAddr2IpPort(ppClientIp, pClientPort, &clientAddr);

 

    return newSockfd;

}

 

/* 得到 TCP 对端地址 */

int w_Getpeername(int sockfd, char** ppClientIp, int *pClientPort){

    struct sockaddr_in clientAddr;

    socklen_t addrLen = sizeof clientAddr;

    int iRet = getpeername(sockfd, (struct sockaddr*)&clientAddr, &addrLen);

 

    w_STAddr2IpPort(ppClientIp, pClientPort, &clientAddr);

 

    return iRet;    

}

 

 

int main()

{

    int epfd, listenfd;    

    /* 仅为示例,没有检查返回值 */

#ifdef DBG_TCP

    // 创建监听套接字

    listenfd = w_Socket(W_TCP);    

    w_Bind(listenfd, LOCAL_IP, LOCAL_PORT);

    listen(listenfd, LISTENQ);

#else

    // 创建普通套接字

    listenfd = w_Socket(W_UDP);    

    w_Bind(listenfd, LOCAL_IP, LOCAL_PORT); /* 指定本地所用的端口 */

#endif

 

    // 创建 epoll

    w_UnblockFd(listenfd);

    epfd = w_EpollCreate(MAXSOCK);

    w_EpollAdd(epfd, listenfd, EPOLLIN); /* 可以加入多个fd,本地程序可监听多个端口 */

 

    struct epoll_event events[MAXEVENTS];

    while (1) {

        //等待epoll事件的发生

        int nfds = w_EpollWait(epfd, events, sizeof events, TIMEOUT);

        if (nfds < 0){

            // TO DO... 处理出错的情况(在UDP收包不收全时会产生错误)

            // 及时退出,否则会导致 CPU 100%

            break;

        }

 

        //处理所发生的所有事件

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

            int fd = w_GetFdFromEvent(&events[i]);

            if (fd < 0) {

                continue; // 出错处理

            }

 

#ifdef DBG_TCP

            /* 监听到的套接字 */

            if (fd == listenfd) {

                char *pIP;

                int port;

                int connfd = w_Accept(listenfd, &pIP, &port);

                if(connfd < 0){

                    continue; // 出错处理

                }

 

                cout << "New connection from " << pIP << ":" << port << endl;

                /* TO DO... */

 

                w_UnblockFd(connfd);

                //添加观测的文件描述符

                w_EpollAdd(epfd, connfd, EPOLLIN); // EPOLLOUT|EPOLLET  

                // LT 最后一批数据和退出事件不会合并

                // ET 最后一次数据可能会和退出合并在一起了

 

// keepalive 探测失败后,会触发2个 IN 事件(只是对超时而言,至于主机不可达或崩溃则是不一样的情况)

//     第一次recv的错误码是 ETIMEDOUT,可以在这里的超时选择关闭fd

//     第二次recv返回0

                // w_SetKeepAlive(connfd, 60, 5, 8); 

                continue;

            }

#endif

            /* 读取数据 */

            if (w_IsPollIn(&events[i])) { 

                int nbytes;

                char buffer[MAXLINE];

 

#ifdef DBG_TCP

                nbytes = recv(fd, buffer, MAXLINE, 0);

                // 可通过 w_Getpeername 获取对方的地址

#else

                // nbytes = recv(fd, buffer, MAXLINE, 0); // 不想获取对端地址可以直接调用recv

                struct sockaddr clientAddr;

                socklen_t addrLen = sizeof clientAddr;                

                /* 配合epoll ET使用时,最好写个循环确保已读取所有数据,否则可能会出错 */

                nbytes = recvfrom(fd, buffer, MAXLINE, 0, &clientAddr, &addrLen);                 

#endif

                if (0 == nbytes) { /* 无法感知网络断开,需要配置心跳包或KEEPALIVE,建议用前者 */

                    // TO DO

                    cout << "Connection closed!" << endl;

                    w_EpollDel(epfd, fd);  /* 忘记close会导致 LT 模式下 CPU 100% */

                    continue;

                }else if (nbytes < 0) {

                    if (errno == ECONNRESET || errno == ETIMEDOUT) {

                        // TO DO

                        cout << "Line broke:" << nbytes << ERRSTR << endl;

                        w_EpollDel(epfd, fd);

                    }else{

                        // TO DO

                        cout << "Recv error:" << nbytes << ERRSTR << endl;

                    }

                    continue;

                } 

 

                // TO DO 正常处理流程

                buffer[nbytes] = '/0';

                // cout << "Read data:" << buffer << endl;

                // w_EpollMod

            }

#if 0

            else if (w_IsPollOut(&events[i])) {  /* EPOLLOUT 事件使用较少,大多数情况下是可写的,除非对端TCP recv 过慢 */

                const char *buffer = "echo";

                send(fd, buffer, sizeof buffer, 0); // udp 得用 sendto

            }

#endif

            else {  /* 其他事件 */

                // TO DO

                cout << "Connection closed abnormal!" << endl;

                w_EpollDel(epfd, fd);

            }

        }

    }

 

    return 0;

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值