多路转接IO/多路复用 select 、poll、 epoll

2023-11-11

多路转接IO
    1.多路复用--》多路转接
    2.作用: I0多路转接可以完成大量描述符的监控,监控的事件:可读事件,可写事件,异常事件
    3.当使用多路转接的时候,多路转接接口发现了某一一个文件描述符就绪的时候,就会通知进程,让进程针对某一个描述符进行操作;其他描述符继续监控;

    4.好处:避免了进程对其他没有就绪的文件描述符进行操作,从而陷入阻塞的情况

select:
    1.作用:对于大量的描述符进行用户关心事件的监控,如果有描述符就绪,则返回描述符,让用户对描述符进行操作;1.1将用户关系的描述符拷贝到内核,内核帮助用户进行监控
    1.2如果内核监控到某个文件描述符事件就绪,则返回该描述符
    1.3用户针对描述符进行操作


2.接口
int select(int nfds, fd_ set* readfds, fd_ set* writefds, fd_ set* exceptfds, strcut timeval* timeout);

    nfds :取值为监控的最大描述符数值+1    作用: 提高selcet的监控效率

    fd_ set :本质是一一个结构体,结构体内部是一个数组fds_ bits :__fd_ mask fds_ bits[__ FD_ SETSIZE /_ NFDBITS];

                比特位的大小取决于宏_ FD_ SETSIZE

    fd_ set 的使用方式是按照位图的方式来进行使用的

    提供4个操作fd_ set位图的函数:

          void FD_ _CLR(int fd, fd_ set *set); // 从事件集合当中删除某一个文件描述符

          int FD_ ISSET(int fd, fd_ set *set);//判断fd描述符,是否在set集合当中  0 :表示没有在集合当中 非0 :表示在集合当中

          void FD_ SET(int fd, fd_ _set *set); //设置fd ,到集合set当中

          void FD_ ZERO(fd_ set *set); // 清空

timeout :超时时间
     timeval == NULL :阻塞监控
     timeval==0(tv_sec==0 tv__usec==0)非阻塞监控

     timeval > 0 :带有超时时间的监控

返回值:
    大于0 :则返回就绪的文件描述符的个数

    等于0 :等待超时了
    小于0 :监控出错
select返回的时候,会将没有就绪的文件描述符从集合当中去除掉;只返回就绪的文件描述符

例子:selectTCP

/*
1.创建侦听套接字
2.绑定地址信息
3.开始监听
4.将侦听套接字添加到select的可读事件集合当中
5. select监听
6.判断是侦听套接字来就绪,还是新的套接字就绪
   6.1有新连接到来,select返回掉了
accept -->》新的套接字描述符-- 》添加到可读事件集合当中去
   6.2有数据到来;recv接收数据
*/
//TcpSvr.hpp
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>

class TcpSvr
{
    public:
        TcpSvr()
        {
            sockfd_ = -1;
        }
        ~TcpSvr()
        {

        }
        //创建套接字
        bool CreateSocket()
        {
            sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(sockfd_ < 0)
            {
                perror("socket");
                return false;
            }

            int i = 1;
            setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(int));
            return true;
        }
        //绑定地址信息
        bool Bind(std::string& ip, uint16_t port)
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
            if(ret < 0)
            {
                perror("bind");
                return false;
            }
            return true;
        }
        //侦听
        bool Listen(int backlog = 5)
        {
            int ret = listen(sockfd_, backlog);
            if(ret < 0)
            {
                perror("listen");
                return false;
            }
            return true;
        }
        //获取连接
        //bool Accept(struct sockaddr_in* peeraddr, int* sockfd)
        //peeraddr:出参,返回客户端的地址信息
        //ts:出参,返回一个TcpSvr类的实例化指针,在这个类的实例化指针当中保存新创建出来的套接字描述符,上层调用者可以使用返回的类的实例化指针和客户端进行通信
        bool Accept(struct sockaddr_in* peeraddr, TcpSvr* ts)
        {
            socklen_t addrlen = sizeof(struct sockaddr_in);
            int serverfd = accept(sockfd_, (struct sockaddr*)peeraddr, &addrlen);
            if(serverfd < 0)
            {
                perror("accept");
                return false;
            }
            ts->sockfd_ = serverfd;
            return true;
        }
        //发起连接(client)
        bool Connect(std::string& ip, uint16_t port)
        {
            struct sockaddr_in destaddr;
            destaddr.sin_family = AF_INET;
            destaddr.sin_port = htons(port);
            destaddr.sin_addr.s_addr = inet_addr(ip.c_str());
            int ret = connect(sockfd_, (struct sockaddr*)&destaddr, sizeof(destaddr));
            if(ret < 0)
            {
                perror("connect");
                return false;
            }
            return true;
        }
        //发送数据
        bool Send(std::string& data)
        {
            int sendsize = send(sockfd_, data.c_str(), data.size(), 0);
            if(sendsize < 0)
            {
                perror("send");
                return false;
            }
            return true;
        }
        //接收数据
        //data:是一个出参,将接收到的数据返回给调用者
        bool Recv(std::string* data)
        {
            char buf[1024] = {0};
            int recvsize = recv(sockfd_, buf, sizeof(buf) - 1, 0);
            if(recvsize < 0)
            {
                perror("recv");
                return false;
            }
            else if(recvsize == 0)
            {
                printf("peer shutdown connect\n");
                return false;
            }
            (*data).assign(buf, recvsize);
            return true;
        }
        //关闭套接字
        void Close()
        {
            close(sockfd_);
            sockfd_ = -1;
        }

        void SetFd(int fd)
        {
            sockfd_ = fd;
        }

        int Getfd()
        {
            return sockfd_;
        }
    private:
        int sockfd_;
};


//selectsvr.hpp
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
#include <vector>

#include "tcpsvr.hpp"

class SelectSvr
{
    public:
        SelectSvr()
        {
            max_fd_ = -1;
            FD_ZERO(&readfds_);
        }

        ~SelectSvr()
        {
        }

        void AddFd(int fd)
        {
            //1.添加
            FD_SET(fd, &readfds_);
            //2.更新最大文件描述符
            if(fd > max_fd_)
            {
                max_fd_ = fd;
            }
        }

        void DeleteFd(int fd)
        {
            //1.删除
            FD_CLR(fd, &readfds_);
            //2.更新最大文件描述符
            for(int i = max_fd_; i >= 0; i--)
            {
                if(FD_ISSET(i, &readfds_) == 0) // 不在
                    continue;
                max_fd_ = i;
                break;
            }
        }

        bool SelectWait(std::vector<TcpSvr>* vec)
        {
            struct timeval tv;
            tv.tv_sec = 0;
            tv.tv_usec = 3000;

            fd_set tmp = readfds_;
            int ret = select(max_fd_ + 1, &tmp, NULL, NULL, &tv);
            if(ret < 0)
            {
                perror("select");
                return false;
            }
            else if(ret == 0)
            {
                printf("select timeout\n");
                return false;
            }

            //正常情况
            for(int i = 0; i < max_fd_; i++)
            {
                if(FD_ISSET(i, &tmp))
                {
                    //返回就绪的文件描述符 i 返回tcp类的对象
                    TcpSvr ts;
                    ts.SetFd(i);
                    vec->push_back(ts);
                }
            }
            return true;
        }
    private:
        int max_fd_;
        fd_set readfds_;
};
//test.cpp
#include "SelectSvr.hpp"

#define CHECK_RET(p) if(p != true){return -1;}

int main()
{
    TcpSvr listen_ts;
    CHECK_RET(listen_ts.CreateSocket());
    CHECK_RET(listen_ts.Bind("0.0.0.0", 19997));
    CHECK_RET(listen_ts.Listen());

    SelectSvr ss;
    ss.AddFd(listen_ts.Getfd());

    while(1)
    {
        //1.监控
        std::vector<TcpSvr> vec;
        if(!ss.SelectWait(&vec))
        {
            continue;
        }

        for(size_t i = 0; i < vec.size(); i++)
        {
            //2.接收新的连接
            if(listen_ts.Getfd() == vec[i].Getfd())
            {
                struct sockaddr_in peeraddr;
                TcpSvr peerts;
                listen_ts.Accept(&peeraddr, &peerts);
                printf("Have a new connection : [ip] : %s [port] : %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

                //将新创建出来的套接字添加到select的事件集合当中去
                ss.AddFd(peerts.Getfd());
            }
            //3.连接上有数据
            else
            {
                std::string data;
                bool ret = vec[i].Recv(&data);
                if(!ret)
                {
                    ss.DeleteFd(vec[i].Getfd());
                    vec[i].Close();
                }
                printf("client send data is [%s]\n", data.c_str());
            }
        }
    }
    return 0;
}

 

select优缺点
    优点:
       1.select遵循的是posix标准,可以跨平台移植
        2.select的超时时间可以精确到微妙

    缺点:
       1.select是轮询遍历的,监控的效率会随着文件描述符的增多而下降
       2.select所能监控的文件描述符是有上限的,上限为1024, 取决于内核当中的_ FD_ SETSIZE宏的值
       3.select监控文件描述符的时候,需要将集合拷贝到内核当中,监控到文件描述符上有事件就绪的时候,同样需要从内核当中拷贝到用户空间,效率会受到影响
      4.select在返回就绪文件描述符的时候,会将未就绪的文件描述符移除掉,导致第:二次在去监控的时候,需要重新添加5.select没有直接告诉程序员哪一个文件描述符就绪了,需要程序员自己在返回的事件集合当中去判断

多路转接之poll 
    1.跨平台移植性不如select, poll只能在linux环境下使用,也是采用轮询遍历的方式,所以效率也没有明显的增加

    2.相较于select改进的点
        2.1不限制监控的文件描述符个数了
        2.2文件描述符对应一个事件结构,告诉poll两件事情
             2.2.1要监控的文件描述符
             2.2.2关心文件描述符的事件

3.接口
     int poll(struct pollfd *fds, nfds_ t nfds, int timeout);
             fds:事件结构数组
            struct pollfd {
                   int fd; /* file descriptor */关心的文件描述符是什么
                   short events; /* requested events */关心文件描述符产生什么事件
                   POLLIN :可读事件
                   POLLOUT :可写事件
                  文件描述符既关心可读事件,也关可写事件,应该将两者进行“按位或”连接,采用的是的位图方式。
                  eg: events = POLLIN | POLLOUT
                 short revents; /* returned events */当关心的文件描述符产生对应关心的事件时:将发生的时间放
                 到revents当中返回给调用者; revents在每次poll监控之初,就会被初始化为空
                 };


返回值:
    小于0 : poll出错了
    等于0 :监控超时
    大于0 :就绪的文件描述符个数

#include <stdio.h>
#include <unistd.h>
#include <poll.h>

int main()
{
    //1.定义事件结构数组
    struct pollfd fd_arr[10];
    fd_arr[0].fd = 0;
    fd_arr[0].events = POLLIN;
    //2.监控
    while(1)
    {
        int ret = poll(fd_arr, 1, 1000);
        if(ret < 0)
        {
            perror("poll");
            return -1;
        }
        else if(ret == 0)
        {
            printf("timeout\n");
            continue;
        }

        //  2.1 当产生可读事件的时候,处理事件
        //fd_arr[10]    fd_arr[0] fd_arr[1]
        for(int i = 0; i < ret; i++)
        {
            if(fd_arr[i].revents == POLLIN)
            {
                char buf[1024] = {0};
                read(fd_arr[i].fd, buf, sizeof(buf) - 1);
                printf("read content is = %s\n", buf);
            }
        }
    }
    return 0;
}

poll优缺点:
优点:
    pol采用了事件结构的方式.简化了代码的编写
    poll不限制文件描述符的个数
   也不需要重新添加文件描述符到事件结构数组当中了
  
缺点:
     poll也是需要轮询遍历事件结构数组,随着文件描述符的增多,而性能下降

     poll不支持跨平台
     poll也没有告诉用户哪一个具体的文件描述符就绪了,需要程序员进行遍历

     poll也是需要将时间结构数组从用户空间拷贝到内核,从内核拷贝到用户空间

多路转接之epoll

接口
    1.创建epoll操作句柄
       int epoll_ create(int size)
           size :本来的含义是定义epoll最大能够监控的文件描述符个数
          linux内核版本2.6.8之后,该参数size就已经被弃用了,内存现在采用的是扩容的方式size是不可以传入负数的! 
           从内核角度分析:在内核当中创建一个结构体, struct eventpoll结构体--》红黑树,双向链表返回值:返回epoll操作句柄,说白了,就是操作struct eventpoll结构体的的钥匙


   2.操作epoll
int epoll_ ctl(int epfd, int op, int fd, struct epoll_ event *event);
         epfd : epoll操作句柄
         op(option) :想让epoll _ct|函数做什么事情
                       EPOLL _CTL. _ADD :添加一个文件描述符对应的事件结构到红黑树当中
                       EPOLL_ CTL _MOD :修改-个已经在红黑树当中的事件结构
                       EPOLL_ CTL. _DEL :从epoll的红黑树当中删除一个文件描述符对应的事件结构
        fd :告诉epoll用户关心的文件描述符
              event :类型是struct epoll_ event结构体,也是epoll的事件结构
                        struct epoll_ event {
                                       uint32_ tevents; /* Epoll events */用户对文件描述符所关心的事件

                                       EPOLLIN :可读事件
                                      EPOLLOUT :可写事件
                                      epoll_ data. t data; /* User data variable */

                                                      }

3.监控
int epoll _wait(int epfd, struct epoll_ event *events, int maxevents, int timeout);
         epfd : epoll操作句柄
         events : epoll事件结构数组--》出参,返回就绪的事件结构(每一个事件结构都对应-个文件描述符)maxevents :最大能够拷贝多少个事件结构
         timeout:
                  大于0 :带有超时时间,单位为毫秒
                  等于0 :非阻塞
                 小于0 :阻塞
         返回值:
                大于0 :返回就绪的文件描述符个数
                 等于0 :等待超时
                 小于0 :监控出错

示例epollTCP

//tcpsvr.hpp
/*
TCP接口
1创建套接字
2绑定地址信息
3监听
4获取新连接
5接收数据
6发送数据
7关闭套C接字

*/
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>

class TcpSvr
{
    public:
        TcpSvr()
        {
            sockfd_ = -1;
        }
        ~TcpSvr()
        {

        }
        //创建套接字
        bool CreateSocket()
        {
            sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if(sockfd_ < 0)
            {
                perror("socket");
                return false;
            }

            int i = 1;
            setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(int));
            return true;
        }
        //绑定地址信息
        bool Bind(const std::string& ip, uint16_t port)
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());
            int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
            if(ret < 0)
            {
                perror("bind");
                return false;
            }
            return true;
        }
        //侦听
        bool Listen(int backlog = 5)
        {
            int ret = listen(sockfd_, backlog);
            if(ret < 0)
            {
                perror("listen");
                return false;
            }
            return true;
        }
        //获取连接
        //bool Accept(struct sockaddr_in* peeraddr, int* sockfd)
        //peeraddr:出参,返回客户端的地址信息
        //ts:出参,返回一个TcpSvr类的实例化指针,在这个类的实例化指针当中保存新创建出来的套接字描述符,上层调用者可以使用返回的类的实例化指针和客户端进行通信
        bool Accept(struct sockaddr_in* peeraddr, TcpSvr* ts)
        {
            socklen_t addrlen = sizeof(struct sockaddr_in);
            int serverfd = accept(sockfd_, (struct sockaddr*)peeraddr, &addrlen);
            if(serverfd < 0)
            {
                perror("accept");
                return false;
            }
            ts->sockfd_ = serverfd;
            return true;
        }
        //发起连接(client)
        bool Connect(std::string& ip, uint16_t port)
        {
            struct sockaddr_in destaddr;
            destaddr.sin_family = AF_INET;
            destaddr.sin_port = htons(port);
            destaddr.sin_addr.s_addr = inet_addr(ip.c_str());
            int ret = connect(sockfd_, (struct sockaddr*)&destaddr, sizeof(destaddr));
            if(ret < 0)
            {
                perror("connect");
                return false;
            }
            return true;
        }
        //发送数据
        bool Send(std::string& data)
        {
            int sendsize = send(sockfd_, data.c_str(), data.size(), 0);
            if(sendsize < 0)
            {
                perror("send");
                return false;
            }
            return true;
        }
        //接收数据
        //data:是一个出参,将接收到的数据返回给调用者
        bool Recv(std::string* data)
        {
            char buf[1024] = {0};
            int recvsize = recv(sockfd_, buf, sizeof(buf) - 1, 0);
            if(recvsize < 0)
            {
                perror("recv");
                return false;
            }
            else if(recvsize == 0)
            {
                printf("peer shutdown connect\n");
                return false;
            }
            (*data).assign(buf, recvsize);
            return true;
        }
        //关闭套接字
        void Close()
        {
            close(sockfd_);
            sockfd_ = -1;
        }

        void SetFd(int fd)
        {
            sockfd_ = fd;
        }

        int Getfd()
        {
            return sockfd_;
        }
    private:
        int sockfd_;
};
//epollsvr.hpp
/*
epoll的封装epoll默认是水平触发模式 LTEpoll_ Svr
   创建epoll操作句柄(放到构造函数当中去)epoll create
   添加对应的事件结构到epoll当中
           epoll_ ctl
   从epol当中删除对应的事件结构
          epoll_ ctl
   监控
epoll_ _wait
成员变量:
int epollfd
*/
#pragma once
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <vector>

#include "tcpsvr.hpp"

class EpollSvr
{
    public:
        EpollSvr()
        {
            epoll_fd_ = -1;
        }

        ~EpollSvr()
        {

        }

        bool InitSvr()
        {
            epoll_fd_ = epoll_create(10);
            if(epoll_fd_ < 0)
            {
                return false;
            }
            return true;
        }

        bool AddFd(int fd)
        {
            //1.组织事件结构
            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = fd;
            int ret = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &ev);
            if(ret < 0)
            {
                perror("epoll_ctl");
                return false;
            }
            return true;
        }

        bool DeleteFd(int fd)
        {
            int ret = epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, NULL);
            if(ret < 0)
            {
                perror("epoll_ctl");
                return false;
            }
            return true;
        }

        bool EpollWait(std::vector<TcpSvr>* out)
        {
            //10
            struct epoll_event fd_arr[10];
            int ret = epoll_wait(epoll_fd_, fd_arr, sizeof(fd_arr)/sizeof(fd_arr[0]), -1);
            if(ret < 0)
            {
                perror("epoll_wait");
                return false;
            }
            else if(ret == 0)
            {
                printf("epollwait timeout\n");
                return false;
            }

            //防止数组越界
            if(ret > sizeof(fd_arr)/sizeof(fd_arr[0]))
            {
                ret = sizeof(fd_arr)/sizeof(fd_arr[0]);
            }

            //剩下的逻辑都是ret大于0的逻辑了
            for(int i = 0; i < ret; i++)
            {
               TcpSvr ts; 
               ts.SetFd(fd_arr[i].data.fd);
               out->push_back(ts);
            }
            return true;
        }
    private:
        //epoll操作句柄
        int epoll_fd_;
};
//test.cpp
/*
1.创建侦听套接字
2.绑定地址信息
3.监听
4.将侦听套接字添加到epoll当中
5.监控  前提:监控的文件描述符产生了就绪事件
6.1获取新连接,将新连接的套接字添加到epoll当中
6.2新连接上有数据到来,获取数据
*/
#include "epoll_lt_svr.hpp"

#define CHECK_RET(p) if(p != true){return -1;}

int main()
{
    TcpSvr listen_ts;
    CHECK_RET(listen_ts.CreateSocket());
    CHECK_RET(listen_ts.Bind("0.0.0.0", 19997));
    CHECK_RET(listen_ts.Listen());

    EpollSvr es;
    CHECK_RET(es.InitSvr());
    es.AddFd(listen_ts.Getfd());

    while(1)
    {
        //1.监控
        std::vector<TcpSvr> vec;
        if(!es.EpollWait(&vec))
        {
            continue;
        }

        for(size_t i = 0; i < vec.size(); i++)
        {
            //2.接收新的连接
            if(listen_ts.Getfd() == vec[i].Getfd())
            {
                struct sockaddr_in peeraddr;
                TcpSvr peerts;
                listen_ts.Accept(&peeraddr, &peerts);
                printf("Have a new connection : [ip] : %s [port] : %d\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port));

                //将新创建出来的套接字添加到select的事件集合当中去
                es.AddFd(peerts.Getfd());
            }
            //3.连接上有数据
            else
            {
                std::string data;
                bool ret = vec[i].Recv(&data);
                if(!ret)
                {
                    es.DeleteFd(vec[i].Getfd());
                    vec[i].Close();
                }
                printf("client send data is [%s]\n", data.c_str());
            }
        }
    }
    return 0;
}

 

epoll对描述符就绪事件的触发方式:
水平触发
    EPOLLLT --> epoll默认工作方式,select和poll都是水平触发
    可读事件:只要接收缓冲区当中的数据大于低水位标记( 1字节),就会一直触发可读事件就绪,直到接收缓冲区当中没有数据可读
    可写事件:只要发送缓冲区当中的数据空间大小大于低水位标记( 1字节) , 就会-直触发可写事件就绪,直到发送缓冲区当中没有空间可写.

边缘触发(边沿触发)
EPOLLET --》只有epoll才支持
使用方式:只需要在文件描述符对应的事件结构当中的关心的事件按位或上EPOLLET就可以了
struct epoll_ event ev;
ev.events = EPOLLIN | EPOLLET;
可读事件:只有当新数据到来的时候,才会触发可读
可写事件:只有发送缓冲区剩余空间从不可写变成可写才会触发一次可写事件就绪

示例epoll_et

#include <stdio.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <string>

int main()
{
    //设置文件描述符属性为非阻塞
    int flag = fcntl(0, F_GETFL);
    fcntl(0, F_SETFL, flag | O_NONBLOCK);

    //1.创建epoll操作句柄
    int epoll_fd = epoll_create(10);
    if(epoll_fd < 0)
    {
        perror("epoll_create");
        return 0;
    }

    //2.添加文件描述符
    struct epoll_event ev;
    //events 使用方式是按照位图的使用方式来使用的
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = 0;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &ev);
    //3.监听
    while(1)
    {
        struct epoll_event fd_arr[10];
        int ret = epoll_wait(epoll_fd, fd_arr, sizeof(fd_arr)/sizeof(fd_arr[0]), -1);
        if(ret < 0)
        {
            perror("epoll_wait");
            //return 0;
            continue;
        }

        for(int i = 0; i < ret; i++)
        {
            if(fd_arr[i].data.fd == 0)
            {
                std::string read_data;
                while(1)
                {
                    char buf[3] = {0};
                    ssize_t readsize = read(0, buf, sizeof(buf) - 1);
                    if(readsize < 0)
                    {
                        if(errno == EAGAIN || errno == EWOULDBLOCK)
                        {
                            //说明数据读完了
                            goto myend;
                        }
                        perror("read");
                        return 0;
                    }

                    read_data += buf;
                    if(readsize < (ssize_t)sizeof(buf) - 1)
                    {
myend:
                        printf("read buf content is %s\n", read_data.c_str());
                        break;
                    }
                }
            }
        }
    }
    //4.处理就绪事件
    return 0;
}

按照读到数据的有效字节来判断
           如果判断read或者recv的返回值比我们准备的buf的最大接收能力还小,说明读完了当我们读到的字节数量和buf的最大接收能力相等,无法判断后面是否还有数据的
ET加上循环,会造成饥饿状态! 
解决方案:将文件描述符设置成为非阻塞状态
        int fcntl(int fd, int cmd, ../* arg */ );
               fd :操作哪-个文件描述符
               cmd :告诉fcnt|函数做什么事情
                  F_ GETFL:获取文件描述符属性

                  F_ SETFD:设置文件描述符属性
 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多路转接IO/多路复用 select 、poll、 epoll 的相关文章

  • python-数据可视化-使用API

    使用Web应用程序编程接口 API 自动请求网站的特定信息而不是整个网页 再对这些信息进行可视化 一 使用Web API Web API是网站的一部分 用于与使用具体URL请求特定信息的程序交互 这种请求称为API调用 请求的数据将以易于处
  • 【知识分享】数据结构的应用——链表

    背景 对于数据结构 其实学过C语言的都不陌生 无外乎就队列 栈 二叉树等等这些 但其实对于初学者最困惑的不是数据结构是怎么样的 而是数据结构有什么用 我自己也是工作好几年后才体验到数据结构的快乐 所以本系列文章重点从应用场景切入 让大家理解
  • android 源码分析

    感谢网友banketree的收集 压缩包的内容如下 1 360新版特性界面源代码 实现了360新版特性界面的效果 主要涉及到Qt的一些事件处理与自定义控件 但源码好像是c 2 aidl跨进程调用 服务端onBind暴露 然后客户端bindS
  • 架构师日记-软件工程里的组织文化

    一 引言 本文是京东到家自动化测试体系建设过程中的一些回顾和总结 删减了部分系统设计与实践的章节 保留了组织与文化相关的内容 整理成文 以飨读者 下面就以QA Quality Assurance 的视角来探讨工作中经常面临的问题与挑战 关于
  • java 代码走查_java代码走查计划书

    java代码走查计划书 由会员分享 可在线阅读 更多相关 java代码走查计划书 8页珍藏版 请在人人文库网上搜索 1 WATER Corporation 代码走查计划书Version 2 0XXX2012 3 20文档修改记录版本号主要作
  • Linux结构,ls命令,cd命令,绝对路径和相对路径,命令类型,环境变量

    Linux结构 软件 IDE shell 软件 库 一个系统调用 可执行程序无法独立执行 library API 内核 通用管理软件 平台 kernel 底层 硬件 在单个CPU上 硬件资源独立 IDE 集成的开发环境 可以让程序员开发程序
  • 【Flask&MySQL】ORM模型的CRUD操作(十二)

    CRUD是 crud是指在做计算处理时的增查改删 增加 Create 读取 Read 更新 Update 删除 Delete 几个单词的首字母简写 crud主要被用在描述软件系统中数据库或者持久层的基本操作功能 一 添加操作 1 创建视图函
  • Maven本地仓库有jar包却无法导入项目

    本地的maven仓库有jar包 setting文件配置没问题 仓库路径没问题 但是无法导入到项目中 任务中总是显示在下载 从网上找了各种方法 其中一个 将本地仓库中的所有 remote repositories 文件删除 解决了问题 记录一
  • layuiajax提交表单控制层代码_表单提交中的post方法和get方法

    我们在Web表单提交 常常需要选择提交方法 这时我们会用到GET和POST方法 但关于它们之间的区别你又知道多少 今天我们就来了解它们 这两方法其实是HTTP协议中的请求方法 关于HTTP协议可以阅读之前我写的 解密Web通信协议 超文本传
  • Qt/GUI/布局/实现窗口折叠效果/且在操作时父窗口尺寸跟随变动

    文章目录 概述 无法resize到小尺寸 可行方案 其他方案 概述 本文旨在 实现如下所示的显示或隐藏 附加选项 的效果 以折的不常用信息和操作项 减少普通用户负担 提升用户体验 在某些软件中此类窗口折叠效果 常用 按钮 来触发 另一种折叠
  • 软考知识:有关ftp协议 的20端口和21端口的作用和区别

    转载 FTP服务器端口详解 搭建ftp的工具有哪些 贝锐花生壳官网 1 FTP服务器 file transfer protocol server 是在互联网上提供文件存储和访问服务的计算机 他们依照ftp协议提供服务 简单地说 支持FTP协
  • UE4地形简单材质球制作,及地形变黑处理办法

    UE4地形简单的材质球制作 及地形变黑处理办法 2 制作地貌材质球 别忙着创建地貌 在之前你得准备制作一个地貌材质球 新建材质 放到Material里面 找几张你会需要到的贴图 任意张 拖到材质球里面去 鼠标右键输入LandscapeLay
  • 感应(异步)电机磁场定向控制MATLAB/Simulink建模

    电机控制系列文章 感应 异步 电机磁场定向控制电流环PI控制参数设计 感应 异步 电机磁场定向控制速度环PI控制参数设计 目录 电机控制系列文章 前言 一 控制器与被控对象的采样周期 二 系统解算器设置 三 被控对象建模 四 控制器建模 1
  • Linux环境打包Qt程序并部署到Docker容器

    Linux环境打包Qt程序并部署到Docker容器 根据项目需要 将UKylin版本的qt应用程序打包并且部署到docker容器中 系统版本 UKylin版本 Ubuntu18 04 Docker镜像 Ubuntu latest 一 打包Q
  • 轴承剥离型故障对应的特征频率

    包络分析技术是轴承早期故障检测和诊断领域的一种非常有效的信号分析技术 这种技术最早由Mechanical Technology 公司于 1970 年代提出 最初叫做高频共振技术 由于这种技术非常有效 很快就获得了广泛的使用 这种技术还被称作
  • Liunx(一)VMware虚拟机安装学习--Liunx学习的准备

    虚拟机安装 初衷 一 安装前的准备 二 虚拟机硬件配置 2 1 打开VMware 选择 创建新的虚拟机 2 2典型安装与自定义安装 2 3 虚拟机兼容性选择 2 4 选择稍后安装操作系统 2 5操作系统的选择 2 6 虚拟机位置与命名 2
  • sqlmap使用教程

    目录 sqlmap基础使用 1 判断是否存在注入 2 判断文本中的请求是否存在注入 3 查询当前用户下的所有数据库 4 获取数据库中的表名 5 获取表中的字段名 6 获取字段内容 7 获取数据库所有用户 8 获取数据库用户的密码 9 获取当
  • 【廖雪峰python入门笔记】tuple_创建

    tuple 1 是另一种有序的列表 中文翻译为 元组 2 tuple 和 list 非常类似 但是 tuple一旦创建完毕 就不能修改了 同样是表示班里同学的名称 用tuple表示如下 t Adam Lisa Bart 创建tuple 创建
  • 小米笔记本电脑Ruby-15.6 2018/2019款【完美黑苹果MacOS 12 Monterey】

    小米笔记本电脑Ruby 15 6 2018 2019 完美黑苹果 MacOS 12 Monterey XiaoMi Book Ruby 15 6 已完美驱动 扬声器 WiFi 蓝牙 触摸板 开关按F9 有线网络 内置读卡器 因影响白果卡蓝牙
  • 【华为OD机试】数组拼接(C++ Python Java)2023 B卷

    题目描述 有多组整数数组 把他们按如下规则拼接成一个新的数组 从每个数组中按顺序取出固定数列的内容 合并到新数组中 已取出的内容将从原始数组中去除 如果该行不足固定长度或者已经为空 则直接取出剩余部分放到新数组中 继续下一行 输入描述 第一

随机推荐

  • 【多模态】8、MDETR

    文章目录 一 背景 二 方法 2 1 DETR 2 2 MDETR 三 效果 3 1 预训练调整后的检测器 3 2 下游任务 论文 MDETR Modulated Detection for End to End Multi Modal U
  • 图标生成器

    https icon wuruihong com 转载于 https www cnblogs com zhaoxinmei 123 p 11083478 html
  • Tensorflow(1)进行多维矩阵的拆分与拼接

    最近在使用tensorflow进行网络训练的时候 需要提取出别人训练好的卷积核的部分层的数据 由于tensorflow中的tensor和python中的list不同 无法直接使用加法进行拼接 后来发现一个函数可以完成tensor的拼接 函数
  • SaToken踩坑记录

    最近在工作过程中频繁使用satoken做权限认证 很好用 但是也带来了一些坑 一 与knife4j搭配使用 问题描述 使用satoken做权限认证后 knife4j的doc html页面无法打开 解决方案 satoken将knife4j的d
  • 使用python自动回复微信消息(基于图像识别无封号风险)

    不同于其他的外挂思路 不使用 itchat 不使用 itchat 不使用 itchat 这个网页登录的渠道已经被微信关闭了 直接正常登录你的电脑微信 然后使用图像识别微信的消息图标 收到消息后可以根据自定义的接口或者字典自动回复消息 这个程
  • 网站下方版权信息的正规写法

    文章目录 格式 其中需注意 一个特别 教科书 版的例子 来自菜鸟教程最下方版权信息 其他写法 写在最后 虹棠包有话说 格式 Copyright 年份 名称 网站首页网址 All Rights Reserved 备案号 其中需注意 年份可以是
  • 计算机信息安全技术课后习题答案3-4章

    目录 第三章 信息认证技术 一 选择题 二 填空题 三 简答题 第四章 计算机病毒 一 选择题 二 填空题 三 简答题 第三章 信息认证技术 一 选择题 身份认证是安全服务中的重要一环 以下关于身份认证的叙述不正确的是 B A 身份认证是授
  • 马来西亚理科大学 计算机 校区,马来西亚理科大学在马来西亚是一个怎样的存在?...

    原标题 马来西亚理科大学在马来西亚是一个怎样的存在 马来西亚理科大学成立于 1969 年 马来西亚理科大学是大马建立的第二所国立大学 也是马来西亚五所研究密集型大学之一 马来西亚理科大学是一所全球排名前 1 的顶尖大学 它的医学 理学 工程
  • 【Unity】虚拟现实 VRTK插件使用教程(六)VR UI (UGUI和VRTK的事件处理机制)

    文章目录 UI 2D UI 画布设置 相机设置 用途 3D UI 画布设置 交互 UGUI事件处理流程 VRTK事件处理流程 VRTK源码解析 UI UI通常按类别分为三种 即2D UI 3D UI 模型UI 2D UI 2D UI固定在屏
  • 设计模式之迭代器模式

    迭代器模式 1 需求 编写程序展示一个学校院系结构 一个学校中有多个院 一个学院中有多个系 效果图 2 解决方案 使用迭代器模式进行遍历 将数据的存储和数据的遍历进行分离 3 迭代器模式的简单介绍 迭代器模式 Iterator Patter
  • 使用sql语句对数据库脱敏

    最近帮领导整理了一下数据库 给数据库的某些字段进行脱敏 现在整理一下 嘻嘻 1 姓名脱敏 update table set 列 REPLACE 列 SUBSTR 列 2 1 姓名脱敏之后的效果 2 手机号脱敏 UPDATE table SE
  • mongo简介——update & findAndModify

    由于发现mongo更新太快 跟我之前所学有很多差异 尤其是聚合操作 比较早的版本只有group和mapreduce两个函数 不支持avg max min sum这些操作 现在除了增加了这些常用操作以外还提供了更加丰富的聚合功能 从这一篇开始
  • 制作插件以及用插件克隆物体

    using System Collections using System Collections Generic using UnityEditor using UnityEngine using UnityEngine UI publi
  • uboot命令使用学习(2)

    学习目标 uboot命令使用学习 2 学习内容 学习使用了正点原子的I MX6ULL教程及开发平台 使用uboot的内存操作命令 直接对DRAM进行读写操作 1 md 2 nm 3 mm 4 mw 5 cp 6 cmp 学习时间 2022
  • CVPR 2022 论文列表(持续更新)

    本文包括论文链接及代码 关注公众号 AI基地 及时获取最新资讯 学习资料 GitHub链接 GitHub gbstack cvpr 2022 papers CVPR 2022 papers with code 因为CSDN的markdown
  • Unity中Pivot和Center的区别与应用

    Pivot指的是轴心 是模型在建模软件中构建时指定的 可以在建模软件中进行更改 如果选择了场景中的多个物体的话 则坐标是第一个选中的物体的Pivot坐标 Center指的是中心 是在Unity中根据模型的mesh信息计算得到的中心位置 是所
  • QML学习二:Doxygen为qml工程生成代码文档

    效果如下 设置后能够支持 js和 qml文档 QML学习二 Doxygen为工程生成注释文档 前言 一 安装doxyqml 二 Doxygen设置 1 文档目录设置 2 文档目录设置 三 添加注释 总结 前言 好的代码必须配一个好的文档说明
  • linux named dns中继,菜鸟linux DNS搭建问题,请指教

    下面是相关配置文件内容 ip为192 168 247 168 root localhost named root localhost named root localhost named cat etc resolv conf Genera
  • KEGG_cnetplot绘制基因—通路图(展示想要的通路)——R

    以KEGG富集结果为例 首先进行KEGG富集分析 library clusterProfiler de ekp lt enricher gene TERM2GENE pathway2gene TERM2NAME pathway2name p
  • 多路转接IO/多路复用 select 、poll、 epoll

    多路转接IO 1 多路复用 多路转接 2 作用 I0多路转接可以完成大量描述符的监控 监控的事件 可读事件 可写事件 异常事件 3 当使用多路转接的时候 多路转接接口发现了某一一个文件描述符就绪的时候 就会通知进程 让进程针对某一个描述符进