• epoll与非阻塞


    一.基本概念

    Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!!

    Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!

    阻塞IO:当你去读一个阻塞的文件描述符时,如果在该文件描述符上没有数据可读,那么它会一直阻塞(通俗一点就是一直卡在调用函数那里),直到有数据可读。当你去写一个阻塞的文件描述符时,如果在该文件描述符上没有空间(通常是缓冲区)可写,那么它会一直阻塞,直到有空间可写。以上的读和写我们统一指在某个文件描述符进行的操作,不单单指真正的读数据,写数据,还包括接收连接accept(),发起连接connect()等操作…

    非阻塞IO:当你去读写一个非阻塞的文件描述符时,不管可不可以读写,它都会立即返回返回成功说明读写操作完成了,返回失败会设置相应errno状态码,根据这个errno可以进一步执行其他处理。它不会像阻塞IO那样,卡在那里不动!!!

    二、简述

    连接socket在边沿触发模式下要采取非阻塞的方式进行读写数据,如果采用阻塞的方式读写,则可能造成死锁
    例如,当从客户端读固定字节数据时,如果客户端发送的数据不足,则会阻塞在读固定字节函数处,要唤醒这种状态,则需要客户端有新的数据发送过来,但此时需要epoll_wait返回才能知道有新的数据被发送过来,而程序阻塞在读固定字节函数处,而非在epoll_wait处,因此可能导致读数据处的阻塞不能被唤醒;
    又例如当向客户端发送数据时,如果客户端的滑动窗口已被填满,此时再向客户端写数据是会阻塞的,只有等待客户端的滑动窗口腾出空间,然后发送信息告知已经处于可写状态时,同理,此时并非阻塞在epoll_wait 处,故不能接收此消息,也就不能唤醒写函数。
    不采用阻塞的读写方式,主要是为了防止死锁

    三、相关概念

    同步和异步
      同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。

    阻塞和非阻塞
      阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。

    四、代码分析

    	//设置为非阻塞
        flag = fcntl(connfd, F_GETFL);
        flag |= O_NONBLOCK;
        fcntl(connfd, F_SETFL, flag);
    
    • 1
    • 2
    • 3
    • 4

    (1)、如果用阻塞IO+while循环,当最后一个数据读取完后,程序是无法立刻跳出while循环的,因为阻塞IO会在 while(true){ int len=recv(); }这里阻塞住,除非对方关闭连接或者recv出错,这样程序就无法继续往下执行,这一次的epoll_wait没有办法处理其它的连接,会造成延迟、并发度下降。如图所示,server收到的数据,一直等client端发送,一直阻塞在while中,等待数据。而非阻塞的方式下,会接收到如aaaa\nbbbb\n,会跳出循环。
    在这里插入图片描述

    (2)、如果是非阻塞IO+while循环,当读取完数据后,recv会立即返回-1,并将errno设置为EAGAIN或EWOULDBLOCK,这就表示数据已经读取完成,已经没有数据了,可以退出循环了。这样就不会像阻塞IO一样卡在那里,这就减少了不必要的等待时间,性能自然更高。如图所示,接收完aaaa\nbbbb\n数据后,会跳出循环,不会像则色IO一样卡在那里,减少了不必要的等待时间。
    在这里插入图片描述
    总结:
    每次读取小数据,所有一切都一样,一次读完返回,不用while。
    读大数据,LT用的很少,几乎都是ET,阻塞IO会卡在while里,非阻塞IO会立即返回,性能更好

    最后补充两种情况,
    1.读数据时,如果缓冲区装满,write 会阻塞住,等待写缓冲区有空余可写,如果网络拥塞,数据迟迟没有被发送走,整个ep oll连接都会中断。
    2.epoll返回可读,数据可能因为各种原因被内核丢弃,这样你read的时候会一直卡在那里,整个epoll我也会被阻塞在那里。

    五、代码

    server.c
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAXLINE 10
    #define SERV_PORT 8000
    
    int main()
    {
        struct sockaddr_in servaddr, cliaddr;
        socklen_t cliaddr_len;
        int listenfd, connfd;
        char buf[MAXLINE];
        char str[INET_ADDRSTRLEN];
        int efd, flag;
    
        listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);
    
        bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
        listen(listenfd, 20);
    
        struct epoll_event event;
        struct epoll_event resevent[10];
    
        efd = epoll_create(10);
        int res, len;
    
        event.events = EPOLLIN | EPOLLET;
    
        printf("Accepting connections ...\n");
        cliaddr_len = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
        printf("received from %s at PORT %d\n",
               inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
               ntohs(cliaddr.sin_port));
    	
    	//设置为非阻塞
        flag = fcntl(connfd, F_GETFL);
        flag |= O_NONBLOCK;
        fcntl(connfd, F_SETFL, flag);
    
        event.data.fd = connfd;
        epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
        while (1)
        {
            printf("epoll_wait begin\n");
            res = epoll_wait(efd, resevent, 10, -1);
            printf("epoll_wait end res %d\n", res);
    
            if (resevent[0].data.fd == connfd)
            {
                while ((len = read(connfd, buf, MAXLINE / 2)) > 0)
                    write(STDOUT_FILENO, buf, len);
            }
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    /* client.c */
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAXLINE 10
    #define SERV_PORT 8000
    
    int main(int argc, char *argv[])
    {
        struct sockaddr_in servaddr;
        char buf[MAXLINE];
        int sockfd, i;
        char ch = 'a';
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
        servaddr.sin_port = htons(SERV_PORT);
    
        connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    
        while (1) {
            //aaaa\n
            for (i = 0; i < MAXLINE/2; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            //bbbb\n
            for (; i < MAXLINE; i++)
                buf[i] = ch;
            buf[i-1] = '\n';
            ch++;
            //aaaa\nbbbb\n
    
            
            write(sockfd, buf, sizeof(buf));
            sleep(6);
        }
    
        close(sockfd);
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
  • 相关阅读:
    Visual Studio Code+drawio扩展插件的安装和使用,免费的软件构图神器
    springboot - 2.7.3版本 - (八)ELK整合Kafka
    资金变动语音通知怎么实现?
    Java系列之:var关键字
    PACP学习笔记一:使用 PCAP 编程
    神经网络属于人工智能的,人工智能神经系统是
    机器学习必修课 - 如何处理缺失数据
    OpenMV输出PWM,实现对舵机控制
    Idea断点调试(debug)详解
    从《区块链技术原理与应用实践》看区块链技术如何激活新场景赋能价值传递
  • 原文地址:https://blog.csdn.net/qq_42853675/article/details/133989000