Linux 网络编程 Epoll概念补全

本章节主要针对于epoll进行一些补充,游双上很多部分并没有给出详尽的解释,并且有一点搬运文档的感觉,针对于部分进行补全;

关于epoll的两种触发方式:

最主要的问题是这问题,以及他们和阻塞的关系及异同;

epoll分为两种触发方式:TL水平触发以及EL边沿触发;

从总体上来看,两者无非是通知的功能不同:

  1. TL:当socket的缓冲区内只要有数据,就会一直发出提示请求;
  2. EL:只有当socket有数据写入的时候,才会发出提示请求;

总而言之,针对于TL来说,只要缓冲区里有数据没有取完,就会一直提示,直到取完为止,而EL是只提示一次,取不取完都不会再提示;

这里有两点会在后续理解和代码中会造成很大的理解困惑:

  1. 为什么EL要和非阻塞结合起来;
  2. 阻塞和epoll的关系到底是什么样的;

为什么EL要和非阻塞结合:

这里翻阅了基本的知乎以及相关博客,发现主要还是因为业务场景造成的;

假如有以下场景:
A给B发送一个100B的消息,但是后50B里面要求B回应A;

假若B采用EL模式,只读了50B,忘了还剩下50B在B的缓冲区里,由于EL只会提示一次,此时就不会得知自己还要回复A;

所以A就一直等,等到死,此时就发生了“死锁”,A陷入盲等;

因此,为了规避该风险,ET模式下会采用非阻塞IO,当得知一个socket可读后,就会采用非阻塞读,一直读,直到读到“EGAIN”错误;

阻塞和epoll的关系到底是怎么样的:

这个就是另外一个派生出来的点,为什么LT模式不用非阻塞呢;

原因是从宏观层面来上看,唯一需要阻塞的只有epoll_wait;

epoll_wait作为主线程一直阻塞,直到红黑树内有socket就绪打入就绪列表,才会唤醒主线程,对返回的socket列表进行操作;

对于LT模式下,阻塞与否并没有太大关系,如果没有取完,在下一次epoll_wait中也会可以再取;

而对于ET模式,则不阻塞很容易陷入client和server死锁盲等状态;

具体例子:

这里说一下游双书上的示例:

#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
#include<sys/epoll.h>
#include<pthread.h>
#include<fcntl.h>


#define MAX_EVENT_MUMBER 1024
#define BUFFER_SIZE 10

int setnonblocking(int fd){
    int old_option=fcntl(fd,F_GETFL);//Get the old statmengt of the file
    int new_option=old_option|O_NONBLOCK;//The new sign;
    fcntl(fd,F_SETFL,new_option);//Set the new statment to this file
    return old_option;
}

void addfd(int epollfd,int fd,bool enable_et){
    epoll_event event;
    event.data.fd=fd;
    event.events=EPOLLIN;
    if(enable_et){
        event.events|=EPOLLET;
    }
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
    setnonblocking(fd);
}

void lt(epoll_event* events,int number,int epollfd,int listenfd){
    char buf[BUFFER_SIZE];
    for(int i=0;i<number;i++){
        int sockfd=events[i].data.fd;
        if(sockfd==listenfd){
            struct sockaddr_in client_address;
            socklen_t clinet_addrlength=sizeof(client_address);
            int connfd=accept(listenfd,(struct sockaddr*)&client_address,&clinet_addrlength);
            addfd(epollfd,connfd,false);//not ET,LT
        }else if(events[i].events&EPOLLIN){
            printf("event trigger once\n");
            memset(buf,'\0',BUFFER_SIZE);
            int ret=recv(sockfd,buf,BUFFER_SIZE-1,0);
            if(ret<=0){
                close(sockfd);
                continue;
            }
            printf("get %d bytes of content:%s\n",ret,buf);
        }else{
            printf("something else happended\n");
        }
    }
}

void et(epoll_event* events,int number,int epollfd,int listenfd){
    char buf[BUFFER_SIZE];
    for(int i=0;i<number;i++){
        int sockfd=events[i].data.fd;
        if(sockfd==listenfd){
            struct sockaddr_in client_address;
            socklen_t clinet_addrlength=sizeof(client_address);
            int connfd=accept(listenfd,(struct sockaddr*)&client_address,&clinet_addrlength);
            addfd(epollfd,connfd,true);//ET mode
        }else if(events[i].events&EPOLLIN){
            printf("event trigger once\n");
            while(1){
                memset(buf,'\0',BUFFER_SIZE);
                int ret=recv(sockfd,buf,BUFFER_SIZE-1,0);
                if(ret<0){
                    if((errno==EAGAIN)||(errno==EWOULDBLOCK)){
                        //data is all
                        printf("read later\n");
                        break;
                    }
                }else if(ret==0){
                    close(sockfd);
                    break;
                }else{
                    printf("get %d bytes of content: %s\n",ret,buf);
                }
            }
            //printf("get %d bytes of content:%s\n",ret,buf);
        }else{
            printf("something else happended\n");
        }
    }
}

int main(int argc,char* argv[]){
    if(argc<=2){
        printf("error argc");
        return 1;
    }
    const char* ip=argv[1];
    int port=atoi(argv[2]);
    struct sockaddr_in address;
    bzero(&address,sizeof(address));
    address.sin_family=AF_INET;
    inet_pton(AF_INET,ip,&address.sin_addr);
    address.sin_port=htons(port);
    int sock=socket(PF_INET,SOCK_STREAM,0);
    assert(sock>=0);
    int ret=bind(sock,(struct sockaddr*)&address,sizeof(address));
    assert(ret!=-1);

    ret=listen(sock,5);
    assert(ret!=-1);

    epoll_event events[MAX_EVENT_MUMBER];
    int epollfd=epoll_create(5);
    assert(epollfd!=-1);
    addfd(epollfd,sock,true);
    while(1){
        int ret=epoll_wait(epollfd,events,MAX_EVENT_MUMBER,-1);
        if(ret<0){
            printf("epoll failure\n");
            break;
        }
        lt(events,ret,epollfd,sock);
        //et mode: et(events,ret,epollfd,sock);
    }
    return 0;
}

这里还有一个盲点,就是针对于socket接受的问题;

之前还迷惑为什么要进行判断:sockfd==listenfd,后来发现自己对于socket创建理解有问题;

对于最初的bind操作,会将端口和listensocket进行绑定,作为监听socket;

当accept接受成功后,返回的其实是一个新的socket,用于标记已建立的连接;

但是对于新的连接,其实还是通过listensocket进行连接;

因此,将listensocket托管给epoll,之后就可以从内部判断是收到了新链接,还是已有socket收到了消息;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值