关于epoll

LT自动挡(水平模式),ET手动挡(epoll垂直模式) 区别

ET 边沿触发的 边沿是EAGAIN错误,属于下降沿触发。

ET的驱动事件依靠socket的sk_sleep等待队列唤醒,这只有在有新包到来才能发生,数据包导致POLLIN,
ACK确认导致 sk_buffer destroy从而导致POLLOUT,
但这不是一对一得关系,是多对一(多个网络包产生一个POLLIN, POLLOUT事件)。

ET模式常见错误:

recv到了合适的长度, 程序处理完毕后就epoll_wait

这时程序可能长期阻塞,因为这时socket的 rev_buffer里还有数据,或对端close了连接,但这些信息都在上次通知了你,你没有处理完,就epoll_wait了

正确做法是:
 recv到了合适的长度, 程序处理; 再次recv, 若果是EAGAIN则epoll_wait。 

使用ET模式时, 程序自己驱动下,会发生socket被关闭的情况,这时要处理EPIPE信号和返回值。(如果不处理EPIPE那么会导致程序core掉)

总结:
ET 使用准则,只有出现EAGAIN错误才调用epoll_wait。

场景:

短连接,短数据包:
这种情形下测试的结果是ET和LT一样的效率。(所以不要以为用了ET效率就有提高)

测试指令

strace -c -fF ./your_prog 

测试结果

后得到信息是 epoll_ctl 消耗的时间比 recv+send 还多。 

总结原因:

整个程序是以网络事件来驱动的,所以每个连接都要epoll_ctl3次; 
如果程序自己主动recv+send, 不行的时候再网络驱动的话,可以节省这些epoll_ctl开销。
对于长连接,大数据包:

因为 LT模式只能设置当时感兴趣的事件(如果不写数据也设置POLLOUT的话,会导致cpu 100%) ,所以要频繁调用epoll_ctl,内核也要多次操作链表,所以效率会比ET模式低。

# 程序范例

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>


#define PORT    3457
#define BACKLOG   100
#define BUF_EV_LEN 150    
#define MAX_EPOLL_FD 8000

static char *policyXML="<cross-domain-policy><allow-access-from domain=/"*/" to-ports=/"*/"/></cross-domain-policy>";  
static char *policeRequestStr="<policy-file-request/>";  


int CreateTcpListenSocket();
int InitEpollFd();
void UseConnectFd(int sockfd);
void setnonblocking(int sock);

static int listenfd;

int main()
{
    signal(SIGPIPE, SIG_IGN);
    /**/pid_t pid;

    if((pid = fork()) < 0){
        printf("End at: %d",__LINE__);
        exit(-1);
    }

    if (pid){
        printf("End at: %d",__LINE__);
        exit(0);
    }
    run();
}

int run(){
    int epoll_fd;
    int nfds;
    int i;

    struct epoll_event events[BUF_EV_LEN];
    struct epoll_event tempEvent;

    int sockConnect;
    struct sockaddr_in remoteAddr;
    int addrLen;

    addrLen = sizeof(struct sockaddr_in);
    epoll_fd = InitEpollFd();

    if (epoll_fd == -1)
    {
        perror("init epoll fd error.");
        printf("End at: %d",__LINE__);
        exit(1);
    }

    printf("begin in loop./n");
    while (1)
    {
        nfds = epoll_wait(epoll_fd, events, BUF_EV_LEN, 1000);
        //sleep(3);
        if(nfds>5) printf("connect num: %d/n", nfds);
        if (nfds == -1)
        {
            printf("End at: %d",__LINE__);
            perror("epoll_wait error.");
            continue;
        }
        for (i = 0; i < nfds; i++)
        {
            if (listenfd == events[i].data.fd)
            {
                //printf("connected success/n");
                sockConnect = accept(events[i].data.fd, (struct sockaddr*)&remoteAddr, &addrLen);
                if (sockConnect == -1)
                {
                    printf("End at: %d",__LINE__);
                    perror("accept error.");
                    continue;
                }
                setnonblocking(sockConnect);
                tempEvent.events = EPOLLIN | EPOLLET;
                tempEvent.data.fd = sockConnect;
                if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockConnect, &tempEvent) < 0)
                {
                    perror("epoll ctl error.");
                    printf("End at: %d",__LINE__);
                    return -1;
                }
            }
            else
            {
                UseConnectFd(events[i].data.fd);
            }
        }
    }

    printf("---------------------------------/n/n");

}

int CreateTcpListenSocket()
{
    int sockfd;
    struct sockaddr_in localAddr;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        perror("create socket fail");
        printf("End at: %d",__LINE__);
        return -1;
    }

    setnonblocking(sockfd);

    bzero(&localAddr, sizeof(localAddr));
    localAddr.sin_family = AF_INET;
    localAddr.sin_port = htons(PORT);
    localAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    unsigned int optval;    
    //设置SO_REUSEADDR选项(服务器快速重起)
    optval = 0x1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, 4);
    /*
    //设置SO_LINGER选项(防范CLOSE_WAIT挂住所有套接字)
    optval1.l_onoff = 0;
    optval1.l_linger = 1;
    setsockopt(listener, SOL_SOCKET, SO_LINGER, &optval1, sizeof(struct linger));

    int nRecvBuf=320*1024;//设置为320K
    setsockopt(listener ,SOL_SOCKET, SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

    int nSendBuf=1024*1024;//设置为640K
    setsockopt(listener ,SOL_SOCKET, SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
    */
    if (bind(sockfd,  (struct sockaddr*)&localAddr, sizeof(struct sockaddr)) == -1)
    {
        perror("bind error");
        printf("End at: %d",__LINE__);
        return -1;
    }

    if (listen(sockfd, BACKLOG) == -1)
    {
        perror("listen error");
        printf("End at: %d",__LINE__);
        return -1;
    }

    return sockfd;
}

int InitEpollFd()
{
    struct rlimit rt;
    rt.rlim_max = rt.rlim_cur = MAX_EPOLL_FD;

    if (setrlimit(RLIMIT_NOFILE, &rt) == -1)
    {
        //perror("setrlimit");
        printf("<br/>     RLIMIT_NOFILE set FAILED: %s     <br/>",strerror(errno));
        //exit(1);
    }
    else 
    {
        printf("设置系统资源参数成功!/n");
    }

    //epoll descriptor
    int s_epfd;
    struct epoll_event ev;


    listenfd = CreateTcpListenSocket();

    if (listenfd == -1)
    {
        perror("create tcp listen socket error");
        printf("End at: %d",__LINE__);
        return -1;
    }

    s_epfd = epoll_create(MAX_EPOLL_FD);
    ev.events = EPOLLIN;
    ev.data.fd = listenfd;
    if (epoll_ctl(s_epfd, EPOLL_CTL_ADD, listenfd, &ev) < 0)
    {
        perror("epoll ctl error");
        printf("End at: %d",__LINE__);
        return -1;
    }

    return s_epfd;
}

void UseConnectFd(int sockfd)
{
    int buffer_size=256;
    char recvBuff[buffer_size+1];
    int recvNum = 0;
    int buff_size = buffer_size*10;
    char *buff=calloc(1,buff_size);

    while(1){
        //memset(recvBuff,'/0',buffer_size);
        recvNum = recv(sockfd, recvBuff, buffer_size, MSG_DONTWAIT);


        if ( recvNum< 0) {
            if (errno == ECONNRESET || errno==ETIMEDOUT) {//ETIMEDOUT可能导致SIGPIPE
                close(sockfd);
            }
            break;
        } else if (recvNum == 0) {
            close(sockfd);
            break;
        }

        //数据超过预定大小,则重新分配内存
        if(recvNum+strlen(buff)>buff_size)
        {
            if((buff=realloc(buff,buff_size+strlen(buff)))==NULL)
            {
                break;
            }
        }
        recvBuff[recvNum]='/0';
        sprintf(buff,"%s%s",buff,recvBuff);
        //printf("%s/n",recvBuff);

        if(recvNum<buffer_size)
        {   
            break;
        }
    }

    if(recvBuff[0]=='0')printf("%s/n",buff);

    if(strcmp(buff,policeRequestStr)==0)
    {
        sendMsg(sockfd, policyXML);
    }else if(strlen(buff)>0){
        sendMsg(sockfd,buff);
    }

    free(buff);
    //printf("message: %s /n", recvBuff);
}

void setnonblocking(int sock)
{
    int opts;   
    opts=fcntl(sock,F_GETFL);
    if(opts<0)  
    {   
        perror("fcntl(sock,GETFL)");
        printf("End at: %d",__LINE__);
        exit(1);
    }

    opts = opts|O_NONBLOCK; 
    if(fcntl(sock,F_SETFL,opts)<0)  
    {   
        perror("fcntl(sock,SETFL,opts)");
        printf("End at: %d",__LINE__);
        exit(1);    
    }    

}

//发送消息给某个连接
int sendMsg(int fd,char *msg)
{
    if(fd<1) return 0;
    while(1){
        int l=send(fd,msg,strlen(msg)+1,MSG_DONTWAIT); 

        if(l<0){
            if(errno==EPIPE){
                printf(">Send pipe error: %d/n",errno);
                printf("%d will close and removed at line %d!/n",fd,__LINE__);
                printf(">Send pipe error, %d closed!/n",fd);
                return -1;
            }
            break;
        }
        if (l <= strlen(msg)+1) {
            //printf("消息'%s'发送失败!错误代码是%d,错误信息是'%s'/n", msg, errno, strerror(errno));
            //return -1;
            break;
        }
    }
    return 1;
}

这里写代码片

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值