【linux】高级IO

1. 五种IO模型

同步IO:

  1. 阻塞式IO (像 read , write ... 都是阻塞IO)
  2. 非阻塞式IO (非阻塞轮询等待)
  3. 信号驱动式IO (信号来了,才知道数据就绪了)
  4. 多路复用/多路转接 (多个读写端共同非阻塞轮询一起等待)

异步IO:

  1. 异步IO:发起IO,但是交给别人完成,完成的时候通知自己读取数据(由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据)

注意:

  1. 任何IO过程中, 都包含两个步骤. 第一是等待 , 第二是拷贝
  2. 在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少

2. 高级IO重要概念

(一)同步通信 与 异步通信

同步和异步关注的是消息通信机制

同步:在发出一个调用时,没有得到结果之前,该调用就不返回. 但是一旦调用返回,就得到返回值了,即由调用者主动等待这个调用的结果

异步:调用在发出之后,这个调用就直接返回了,所以没有返回结果,即当一个异步过程调用发出后,调用者不会立刻得到结果,而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

(二)阻塞 vs 非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态

阻塞调用是指调用结果返回之前,当前线程会被挂起. 调用线程只有在得到结果之后才会返回

非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

3. 非阻塞IO

fcntl 函数

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );

函数参数:

fd : 文件描述符

cmd : 可以设置不同值,有不同作用

cmd 设置:

复制一个现有的描述符(cmd=F_DUPFD)

获得/设置文件描述符标记(cmd=F_GETFD或F_SETFD)

获得/设置文件状态标记(cmd=F_GETFL或F_SETFL)

文件状态标记:文件打开时的状态标记,如可读,可写,阻塞等

获得/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)

获得/设置记录锁(cmd=F_GETLK,F_SETLK或F_SETLKW)

代码(非阻塞IO)

void SetNoBlock(int fd) 
{ 
 int fl = fcntl(fd, F_GETFL); //得到 fd 中原来的文件状态标记
 if (fl < 0) 
 { 
 perror("fcntl");
 return; 
 }
 fcntl(fd, F_SETFL, fl | O_NONBLOCK); // 既保留原来的文件状态标志,也增加设置了非阻塞
}

注意:

  1. cmd 设置 O_NONBLOCK 代表非阻塞
  2. cmd 设置一个fd成为非阻塞IO时,当 read读取数据为空时,不会阻塞,而是设置 errno 为 EWOULDBLOCK

4. IO多路转接 -- select

(一)初识select

系统提供select函数来实现多路复用输入/输出模型

select系统调用是用来让我们的程序监视多个文件描述符的状态变化的

程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

(二)select函数

select 函数

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数:

nfd : 需要监视的最大的文件描述符值+1

readfds , writefds , exceptfds :都是输入输出型参数

timeout : 输入输出型参数 ,表示等待的最长时间,是一个结构体,如果期间没有接收到有数据就绪,时间一到,函数会自动返回

返回值:返回值 > 0,代表有多少数据就绪 ; 返回值 = 0,在设置的等待最长时间内,没有数据就绪 ; 返回值 = -1,在等待过程中,有 IO出现错误

注意:

  1. readfds , writefds , exceptfds 都是输入输出型参数,实际上传入的类似一张位图,以readfds参数举例,传入的位图代表这张位图,所有设置过的位置(1代表被设置,0代表没被设置 ),都需要判断是否读就绪(读缓冲区是否有数据),传出的位图代表,只有就绪的位置会被设置,输入输出代表含义不一样如果传入设置成NULL,即代表不关心读缓冲区是否有数据,writefds , exceptfds 的参数设置和前面的类似
  2. timeout 输入等待时间,输出代表剩下的时间,如:设置 5 s ,有一个数据就绪,函数就会返回,等待了 2 s , 输出得到的就是 3 s 。 如果设置成 NULL,即阻塞等待,直到有一个数据就绪

timeval 结构体:

提供了一组操作fd_set的接口, 来比较方便的操作位图:

void FD_CLR(int fd, fd_set *set); // 用来清除描述词组set中相关fd 的位

int FD_ISSET(int fd, fd_set *set); // 用来测试描述词组set中相关fd 的位是否为真

void FD_SET(int fd, fd_set *set); // 用来设置描述词组set中相关fd的位

void FD_ZERO(fd_set *set); // 用来清除描述词组set的全部位

(三)select 使用

代码 (对连接和读做了处理)

#include"SelectServer.h"
int main()
{
    unique_ptr<select_server> str(new select_server());
    str->Init();
    str->start();
}

#include"server.h"
#include <sys/select.h> 
class select_server
{
public:
    select_server()
    {
        for(int i = 0;i < 1024;i++)
        {
            blank[i] = -1;
        }
    }
    void Init()
    {
       _listen.start();
    }
    void Handler(fd_set& set)
    {
        if (FD_ISSET(blank[0], &set))
        {
            int lietent_fd = _listen.ListenFd();
            sockaddr_in x;
            socklen_t len = sizeof(x);
            int fd = accept(lietent_fd, (struct sockaddr *)&x, &len);
            if (fd < 0)
            {
                cerr << "accept fail" << endl;
                return;
            }
            else
            {
                int i = 1;
                for (; i < 1024; i++)
                {
                    if (blank[i] == -1)
                    {
                        cout << "blank[i] = fd" << endl;
                        blank[i] = fd;
                        break;
                    }
                }
                if (i == 1024)
                {
                    cout << "over buff" << endl;
                    close(fd);
                }
            }
        }

        for(int i = 1;i < 1024;i++)
        {
            if(blank[i] == -1)
            {
                continue;
            }
            if(FD_ISSET(blank[i],&set))
            {
                char buff[1024];
                int n = read(blank[i],buff,sizeof(buff) - 1);
                if(n > 0)
                {
                   buff[n] = 0;
                   cout << buff << endl;
                }
                else if(n == 0)
                {
                    cout << "write close" << endl;
                    close(blank[i]);
                    blank[i] = -1;
                }
                else
                {
                    cerr << " read fail " << endl;
                }
            }
        }

    }
    void start()
    {
        int max_fd = -1;
        int lietent_fd = _listen.ListenFd();
        blank[0] = lietent_fd;
        fd_set set;
       while (true)
       {
            FD_ZERO(&set);
           for(int i = 0;i < 1024;i++)
           {
                if(blank[i] != -1)
                {
                   max_fd = max(max_fd,blank[i]);
                   FD_SET(blank[i], &set);
                }
           }
           timeval time({5, 0});
           int n = select(max_fd + 1, &set, nullptr, nullptr, &time);
           if (n > 0)
           {
              cout << " get a link ... " << endl;
              Handler(set);
           }
           else if (n == 0)
           {
               cout << " wait ... ... " << endl;
           }
           else
           {
               cerr << "select fail " << endl;
           }
       }
    }
    ~select_server()
    {}
private:
    int blank[1024];
    server _listen;
};

#include<iostream>
#include<sys/types.h> 
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<signal.h>
#include<memory>
using namespace std;
class server
{
public:
     server(const uint16_t port = 8080,const string& ip = "0.0.0.0")
     :server_port(port)
     ,server_ip(ip)
     {}
     void start()
     {
        //构建套接字
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd < 0)
        {
            cout << "server scoket fail" << endl;
            exit(1);
        }
        else
        {
            cout << "socket success" << endl;
        }
        
        //绑定端口号
        sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
        socklen_t len = sizeof(server);
        int tmp = bind(server_fd,(struct sockaddr*)&server,len);
        if(tmp < 0)
        {
            cout << "server bind fail" << endl;
            exit(2);
        }
        
        
        //开始监听
        int n  = listen(server_fd,10);
        if(n < 0)
        {
            cout << "server listen fail" << endl;
            exit(3);
        }

       

     }
     void run()
     {
         signal(SIGCHLD,SIG_IGN);
         while(true)
         {
             // 等待客户端
             sockaddr_in client;
             socklen_t len = sizeof(client);
             int cilent_id = accept(server_fd, (struct sockaddr *)&client, &len);
             if(cilent_id < 0)
             {
                cout << "server : accept fail" << endl;
             }
             cout << "get a new link ...." << endl;
             int id = fork();
             
             if (id == 0)
             {
                 close(server_fd);
                 while (true)
                 {
                     // 读取数据
                     char buff[1024];
                     int n = read(cilent_id, buff, sizeof(buff));
                     if (n < 0)
                     {
                         cout << "server read fail" << endl;
                         break;
                     }
                     buff[n] = 0;
                     cout << buff << endl;

                     // 写入数据
                     string s = "server say : ";
                     s += buff;
                     write(cilent_id, s.c_str(), s.size());
                 }
                 exit(0);
             }
             close(cilent_id);
            
         }
     }
     int ListenFd()
     {
        return server_fd;
     }
private:
     int server_fd;
     uint16_t server_port;
     string server_ip;
};

(四)select 缺点

  1. 等待的fd是有上限的(最多只能等待1024个)
  2. 输入输出型参数较多,数据拷贝频繁
  3. 输入输出型参数较多,每次都要对关心的数据进行事件重置(重新设置关心的读数据,写数据,或者出错数据)
  4. 用户层使用第三方数组管理用户fd,需要遍历很多次;内核检测fd是否就绪,也需要遍历很多次

5. IO多路转接 -- poll

(一)poll函数

poll 函数

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:

fds:一个结构体地址,存放fd信息,关心数据信息,是否关心数据就绪信息

nfds:struct pollfd 结构体个数

timeout : 等待时间 (设置成 0 ,是非阻塞等待;设置成 - 1,是阻塞等待)

返回值:

返回值小于0, 表示出错; 返回值等于0, 表示poll函数等待超时; 返回值大于0, 表示poll由于文件描述符就绪而返回

注意:

timeout 参数代表的数据,单位是毫秒

// pollfd结构

struct pollfd {

int fd; /* file descriptor */

short events; /* requested events */

short revents; /* returned events */

};

struct pollfd

{

int fd; //fd信息

short events; /* requested events */

short revents; /* returned events */

};

注意:

这些取值实际上是宏

(二)poll 使用

代码 (对连接和读做了处理)

#include"PollServer.h"
int main()
{
    unique_ptr<poll_server> ptr(new poll_server());
    ptr->Init();
    ptr->start();
    return 0;
}

#include"server.h"
#include"poll.h"
class poll_server
{
public:
    poll_server()
    {
        for(int i = 0;i < 1024;i++)
        {
            polls[i].fd = -1;
        }
    }
    void Init()
    {
       sv.Init();
    }
    void Handler()
    {
        for(int i = 0;i < 1024;i++)
        {
            int fd = polls[i].fd;
            if(fd == -1)
            {
                continue;
            }
            else if(fd == sv.ListenFd())
            {
                if(polls[i].revents & POLL_IN)
                {
                    int client_fd = sv.Accept();
                    for(int j = 1;j < 1024;j++)
                    {
                        if(polls[j].fd == -1)
                        {
                            polls[j].events = POLL_IN;;
                            polls[j].fd = client_fd;
                            break;
                        }
                    }
                }
            }
            else
            {
                if(polls[i].revents & POLL_IN)
                {
                    //读数据
                    char buff[1024];
                    int n = read(fd,buff,sizeof(buff) - 1);
                    if(n > 0)
                    {
                        buff[n] = 0;
                        cout << buff << endl;
                    }
                    else if(n == 0)
                    {
                        close(polls[i].fd);
                        polls[i].fd = -1;
                        cout << "client read close" << endl;
                    }
                    else
                    {
                        close(polls[i].fd);
                        polls[i].fd = -1;
                        cout << "read fail" << endl;
                    }
                }
            }
        }
    }
    void start()
    {
       int fd = sv.ListenFd();
       polls[0].fd = fd;
       polls[0].events = POLL_IN;
       while (true)
       {
           int n = poll(polls, 1024, 3000);
           if (n > 0)
           {
             Handler();
           }
           else if (n == 0)
           {
              cout << "wait ..." << endl;
           }
           else
           {
               cerr << "poll fail" << endl;
           }
       }
    }
    ~poll_server()
    {}

private:
    server sv;
    pollfd polls[1024];
};

#include<iostream>
#include<sys/types.h> 
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<signal.h>
#include<memory>
using namespace std;
class server
{
public:
     server(const uint16_t port = 8080,const string& ip = "0.0.0.0")
     :server_port(port)
     ,server_ip(ip)
     {}
     void Init()
     {
        //构建套接字
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd < 0)
        {
            cout << "server scoket fail" << endl;
            exit(1);
        }
        else
        {
            cout << "socket success" << endl;
        }
        
        //绑定端口号
        sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
        socklen_t len = sizeof(server);
        int tmp = bind(server_fd,(struct sockaddr*)&server,len);
        if(tmp < 0)
        {
            cout << "server bind fail" << endl;
            exit(2);
        }
        
        
        //开始监听
        int n  = listen(server_fd,10);
        if(n < 0)
        {
            cout << "server listen fail" << endl;
            exit(3);
        }

       

     }
     int Accept()
     {
         sockaddr_in client;
         socklen_t len = sizeof(client);
         int cilent_id = accept(server_fd, (struct sockaddr *)&client, &len);
         if (cilent_id < 0)
         {
             cout << "server : accept fail" << endl;
         }
         else
         {
            cout << "get a new link ...." << endl;
         }
        return cilent_id;
     }
     void run()
     {
         signal(SIGCHLD,SIG_IGN);
         while(true)
         {
            int cilent_id = Accept();
             // 等待客户端
             int id = fork();
             
             if (id == 0)
             {
                 close(server_fd);
                 while (true)
                 {
                     // 读取数据
                     char buff[1024];
                     int n = read(cilent_id, buff, sizeof(buff));
                     if (n < 0)
                     {
                         cout << "server read fail" << endl;
                         break;
                     }
                     buff[n] = 0;
                     cout << buff << endl;

                     // 写入数据
                     string s = "server say : ";
                     s += buff;
                     write(cilent_id, s.c_str(), s.size());
                 }
                 exit(0);
             }
             close(cilent_id);
            
         }
     }
     int ListenFd()
     {
        return server_fd;
     }
private:
     int server_fd;
     uint16_t server_port;
     string server_ip;
};

(三)poll的缺点

poll中监听的文件描述符数目增多时,和select函数一样,poll返回后,需要轮询pollfd来获取就绪的描述符. 因此随着监视的描述符数量的增长, 其效率也会线性下降

6. IO多路转接 -- epoll

(一)epoll初识

是为处理大批量等待fd而作了改进的poll

优化了 select 和 poll 的缺点

(二)epoll的相关系统调用

epoll_create 函数

作用

创建一个epoll的模型

原型

int epoll_create(int size);

参数:

size : 填写的值大于0即可

注意:

用完之后, 必须调用close()关闭

epoll_ctl 函数

作用

epoll的事件注册函数,增减关心的数据

原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数:

epfd : epoll_create()的返回值(epoll的句柄).

op :表示动作, 用三个宏来表示

fd : 需要监听的fd

event : 告诉内核需要监听什么事

第二个参数的取值:

EPOLL_CTL_ADD :注册新的fd到epfd中

EPOLL_CTL_MOD :修改已经注册的fd的监听事件

EPOLL_CTL_DEL :从epfd中删除一个fd

struct epoll_event结构如图:

events可以是以下几个宏的集合:

EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭)

EPOLLOUT : 表示对应的文件描述符可以写

EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来)

EPOLLERR : 表示对应的文件描述符发生错误

EPOLLHUP : 表示对应的文件描述符被挂断

EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的

EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要

再次把这个socket加入到EPOLL队列里

epoll_wait 函数

作用

收集在epoll监控的事件中已经发送的事件

原型

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

参数:

epfd :epoll_create() 的返回值

events : 是分配好的epoll_event结构体数组

maxevents : 告诉内核这个events有多大

timeout :超时时间 (毫秒,0会立即返回,-1是永久阻塞)

返回值:

函数调用成功,返回对应IO上已准备好的文件描述符数目;返回0表示已超时;返回小于0表示函数失败

注意:

epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).

(三)epoll 工作原理

实际上,创建epoll,等于创建一个文件,文件里面储存了上述信息

(四)epoll 使用

代码

#include"EpollServer.h"
int main()
{
    unique_ptr<epoll_server> ptr(new epoll_server());
    ptr->Init();
    ptr->start();
    return 0;
}

#include"server.h"
#include <sys/epoll.h>
class epoll_server
{
public:
    epoll_server()
    {
        for(int i = 0;i < 1024;i++)
        {
            events[i].data.fd = -1;
        }
    }
    void Init()
    {
        sv.Init();
        epoll_fd = epoll_create(2);
        if( epoll_fd < 0)
        {
            cerr << "epoll_create fail" << endl;
            exit(1);
        }
        cout << "epoll_fd :" << epoll_fd << endl;
    }
    void EpollCtl(int op,int fd,epoll_event* event)
    {

        if(op == EPOLL_CTL_DEL)
        {
            //删除最后一个传参是 nullptr,为了更方便显示细节,就不合并了
            int n = epoll_ctl(epoll_fd,op,fd,nullptr);
            if(n < 0)
            {
                cerr << "epoll_ctl delete fail" << endl;
            }
        }
        else
        {
            int n = epoll_ctl(epoll_fd,op,fd,event);
            if(n < 0)
            {
                cerr << "epoll_ctl add fail" << endl;
            }
        }
    }
    void Handler(int n)
    {
        for (int i = 0; i < n; i++)
        {
            int fd = events[i].data.fd;
            cout << "fd: "<< fd << endl;
            if (fd == sv.ListenFd() && events[i].events & EPOLLIN)
            {
                int client_fd = sv.Accept();
                epoll_event event;
                event.events = EPOLLIN;
                event.data.fd = client_fd;
                cout << "client_fd:" << client_fd << endl;
                EpollCtl(EPOLL_CTL_ADD,client_fd, &event);
            }
            else
            {
                
                if(events[i].events & EPOLLIN)
                {
                    //写端
                    char buff[1024];
                    int n = read(fd,buff,sizeof(buff) - 1);
                    if(n > 0)
                    {
                        buff[n] = 0;
                        cout << buff << endl;
                    }
                    else if(n == 0)
                    {
                        EpollCtl(EPOLL_CTL_DEL,fd, nullptr);
                        close(fd);
                        cerr << "write close" << endl;
                    }
                    else
                    {
                        EpollCtl(EPOLL_CTL_DEL,fd, nullptr);
                        close(fd);
                        cerr << "read fail" << endl;
                    }
                    
                }
            }
        }
    }
    void start()
    {
        int fd = sv.ListenFd();
        epoll_event event;
        event.events = EPOLLIN;
        event.data.fd = fd;
        EpollCtl(EPOLL_CTL_ADD,fd,&event);
        while (true)
        {
            int n = epoll_wait(epoll_fd, events, 1024, 3000);
            if (n > 0)
            {
                Handler(n);
            }
            else if (n == 0)
            {
                cout << "wait ..." << endl;
            }
            else
            {
                cerr << "epoll_wait fail" << endl;
            }
        }
    }
    ~epoll_server()
    {}
private:
    int epoll_fd;
    epoll_event events[1024];
    server sv;
};

#include<iostream>
#include<sys/types.h> 
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<signal.h>
#include<memory>
using namespace std;
class server
{
public:
     server(const uint16_t port = 8080,const string& ip = "0.0.0.0")
     :server_port(port)
     ,server_ip(ip)
     {}
     void Init()
     {
        //构建套接字
        server_fd = socket(AF_INET,SOCK_STREAM,0);
        if(server_fd < 0)
        {
            cout << "server scoket fail" << endl;
            exit(1);
        }
        else
        {
            cout << "socket success" << endl;
        }
        
        //绑定端口号
        sockaddr_in server;
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port);
        server.sin_addr.s_addr = inet_addr(server_ip.c_str());
        socklen_t len = sizeof(server);
        int tmp = bind(server_fd,(struct sockaddr*)&server,len);
        if(tmp < 0)
        {
            cout << "server bind fail" << endl;
            exit(2);
        }
        
        
        //开始监听
        int n  = listen(server_fd,10);
        if(n < 0)
        {
            cout << "server listen fail" << endl;
            exit(3);
        }

       

     }
     int Accept()
     {
         sockaddr_in client;
         socklen_t len = sizeof(client);
         int cilent_id = accept(server_fd, (struct sockaddr *)&client, &len);
         if (cilent_id < 0)
         {
             cout << "server : accept fail" << endl;
         }
         else
         {
            cout << "get a new link ...." << endl;
         }
        return cilent_id;
     }
     void run()
     {
         signal(SIGCHLD,SIG_IGN);
         while(true)
         {
            int cilent_id = Accept();
             // 等待客户端
             int id = fork();
             
             if (id == 0)
             {
                 close(server_fd);
                 while (true)
                 {
                     // 读取数据
                     char buff[1024];
                     int n = read(cilent_id, buff, sizeof(buff));
                     if (n < 0)
                     {
                         cout << "server read fail" << endl;
                         break;
                     }
                     buff[n] = 0;
                     cout << buff << endl;

                     // 写入数据
                     string s = "server say : ";
                     s += buff;
                     write(cilent_id, s.c_str(), s.size());
                 }
                 exit(0);
             }
             close(cilent_id);
            
         }
     }
     int ListenFd()
     {
        return server_fd;
     }
private:
     int server_fd;
     uint16_t server_port;
     string server_ip;
};

(五)epoll的优点(和 select 的缺点对应)

  1. 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
  2. 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
  3. 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,
  4. epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
  5. 没有数量限制: 文件描述符数目无上限

7. 水平触发 和 边缘触发

(一)水平触发Level Triggered 工作模式

epoll默认状态下就是LT工作模式

当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分,当发生上面两者情况,下一次调用 epoll_wait 时,事件依然处于就绪中,值得数据全部被处理完,epoll_wait 才不会提醒

如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait

举例:

向一个接收缓冲区内发送数据,如果上层没有读,或者没读完,检测 epoll_wait 的时候仍然能检测到,直到这个接收缓冲区内的数据全部读完,就不会接收到提醒

(二)边缘触发Edge Triggered工作模式

如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式.

当数据发生变化时(即从无到有,从有到多),检测该数据是否会就绪只会提醒一次,如果数据仍然没有读完,那么 epoll_wait 不再提醒,除非下次数据发生变化

举例:

向一个接收缓冲区内发送数据,第一次检测 epoll_wait 是能检测成功的,如果不读,或者只读一部分,后面再次检测 epoll_wait 是收不到提示的,除非再向这个接收缓冲区内发送数据,则下一次 epoll_wait 是能检测成功的

(三)对比LT和ET

LT是 epoll 的默认行为

ET看上去效率更高,因为使用 ET 能够减少 epoll 触发的次数. 但是代价是:为了防止数据不完整,必须一次响应就绪,就立刻把所有的数据都处理完

LT 也能提高效率:接收到一次提醒后,也把所有数据都进行处理

要做到上述,就必须使用处理数据使用非阻塞等待,即对 fd 使用 fcntl 函数,设置成O_NONBLOCK

相关推荐
小流年 °1 小时前
抓包工具Wireshark
网络·测试工具·wireshark
是理不是里_1 小时前
常见的网络协议汇总(涵盖了不同的网络层次)
网络·网络协议
命里有定数2 小时前
Ubuntu问题 -- 设置ubuntu的IP为静态IP (图形化界面设置) 小白友好
linux·tcp/ip·ubuntu·ip
荆棘鸟骑士2 小时前
Linux修改/etc/hosts不起作用(ping: xxx: Name or service not known)的解决方法——开启NSCD
linux
猫猫不是喵喵.2 小时前
【Linux】Linux入门实操——进程管理(重点)
linux·运维·服务器
Web极客码3 小时前
如何修复WordPress卡在维护模式
服务器·网络·github
jackknight3 小时前
浪潮云启操作系统(InLinux) bcache宕机问题分析
linux
基极向上的三极管4 小时前
【Linux学习】【Ubuntu入门】1-8 ubuntu下压缩与解压缩
linux·ubuntu
送南阳马生序4 小时前
Linux 命令之 tar
linux
high20115 小时前
【Apache Paimon】-- 5 -- Flink 向 Paimon 表写入数据
linux·flink·apache·paimon