【计算机网络】高级IO——select

news2024/12/22 7:10:53

文章目录

  • 1. select函数介绍
    • 为什么要有select?
    • select 接口
      • 第一个参数 nfds的理解
      • 什么是 输入 输出型参数
      • 最后一个参数 timeout 的理解
      • readfds writefds exceptfds 参数的理解
      • select的返回值
  • 2. select的使用
    • SelectServer_v1
      • start 最初版本
      • start 最终版本
      • HandlerEvent函数——处理就绪事件
        • 第一种情况
          • Accepter函数
        • 第二种情况
      • 完整代码
        • SelectServer.hpp
        • Err.hpp(错误信息)
        • Log.hpp(日志打印)
        • main.cc(主函数调用)
        • makefile
        • Sock.hpp(套接字接口实现)
    • SelectServer_v2
      • start的改写
      • HandlerEvent函数的改写
      • 完整代码
  • 3. select 特点
  • 4. select 缺点

1. select函数介绍

为什么要有select?

read/recv 等 文件接口只有一个文件描述符
想要 让一个接口等待多个文件描述符,而read等接口是不具备这个能力的
操作系统就设计一个接口 select,用于多路复用


select 作用
1.等待多个文件描述符
2.只负责等(没有数据拷贝的能力)


select 接口

输入 man select

由于select只负责等待,不负责拷贝,所以没有缓冲区

第一个参数 nfds的理解

第一个参数 nfds,是一个输入型参数 ,表示 select等待的多个文件描述符(fd)数字层面 最大的+1
(文件描述符的本质为 数组下标,多个文件描述符中 数值最大的文件描述符值+1 即nfds )

什么是 输入 输出型参数

用户把数据交给操作系统,同样操作系统也要 通过这些输出型参数 把结果 交给用户
为了让 用户 和 操作系统之间进行信息传递,就把参数设置为 输入 输出型参数

最后一个参数 timeout 的理解

timeout 是一个 输入 输出型参数


timeout的数据类型 为struct timeval

可以一个时间结构体,tv_sec 表示 秒, tv_usec 表示 微秒


对于 struct timeval的对象 可设置三种值

第一种 对象被设为 NULL ,对于select来说 表示 阻塞等待
(多个文件描述符任何一个都不就绪,select就一直不返回)

第二种 struct timeval对象定义出来,并将其中变量都设为0
对于select来说 表示 非阻塞等待
(多个文件描述符任何一个都不就绪,select就会立马出错 并返回)

第三种 struct timeval对象定义出来,并将其中变量设为 5 和 0
表示 5s以内 阻塞等待,否则 就 timeout(非阻塞等待) 一次
若在第3s时 有一个文件描述符就绪,则select就会返回 其中参数 timeout 表示 剩余的时间 2s(5-3=2)


readfds writefds exceptfds 参数的理解

readfds writefds exceptfds 这三个参数 是同质的
readfds 表示 读事件
writefds 表示 写事件
excepttfds表示 异常事件

三者类型都为 fd_set


fd_set是一个位图结构,用其表示多个文件描述符
通过比特位的位置, 就代表文件描述符数值是谁


位图结构想要使用 按位与、按位或 这些操作,必须使用操作系统提供的接口

FD_CLR :将指定的文件描述符从 指定的集合中清除

FD_ISSET:判断文件描述符是否在该集合中被添加

FD_SET: 将一个文件描述符添加到对应的set集合中

FD_ZERO:将文件描述符整体清空


以readfds 读事件为例

若放入 readfds 集合中,用户告诉内核 ,那些文件描述符对应的读事件需要由 内核 来关心
返回时,内核要告诉用户,那些文件描述符的读事件已经就绪


假设想让操作系统去关心八个文件描述符对应的事件

用户想告诉内核时,用户需 定义 fd_set 对象 rfds ,其中八个比特位设置为1
比特位的位置表示几号文件描述符
比特位被置1,则操作系统就需要关心 对应的几号文件描述符
如:需要关心 1-8号文件描述符,即查看是否就绪


当select返回时, 内核会告诉用户,rfds重置,并将 就绪的文件描述符 对应 的 比特位位置 置1

如: 3号和5号就绪,则对应比特位 位置 置1 ,表示3号和5号文件描述符 对应的内容就绪


select的返回值

select的返回值 同样也有三种情况
第一种 大于0
表示有几个文件描述符 是就绪的

第二种 等于0
进入timeout状态 ,即 5s以内没有任何一个文件描述符 就绪

第三种 小于0
等待失败 返回-1
如:想要等待下标为1 和2的文件描述符,但是下标为2的文件描述符根本不存在,就会等待失败


2. select的使用

本次实现分为V1版本V2版本
V1版本 只能为 读事件
而V1版本 除了有读事件 还可以处理 写事件和异常事件
同时两者只有selectserver.hpp有区别,其他都是一样的


SelectServer_v1

start 最初版本

首先设置一个rfds集合,用来表示读集合
在使用 FD_ZERO 将读集合清空
将listensock套接字 添加到 读集合rfds中

由于当前只看读集合,所以写 和异常集合都为空,同时将timeout设置为阻塞等待,即输入才返回
slect的返回值 用n接收 ,使用switch case 来区分 返回值


创建 HandlerEvent函数,来处理就绪事件
因为只有n大于0时,才有文件描述符就绪,所以将其放入default中


start 最终版本

但是这样是存在问题的,当不断会有listensock套接字就绪时,文件描述符会变多 即将套接字 放入 rfds集合中
当select返回时,rfds大部分位图可能会被清空,就没办法保证 对之前的文件描述符有持续的监控能力


select服务器,在使用的时候,需要程序员自己维护一个第三方数组,来进行已经获得的sock进行管理


先通过typedef 将int类型定义 为type_t
定义出一个整形数组 fdarray,并设置数组大小为N (位图大小)


将listensock套接字的文件描述符 作为fdarray 数组的第一个元素
由于select函数的第一个函数 是所有文件描述符 最大值+1
所以通过maxfd 记录最大值

每次都从数组下标为0的位置处开始 向后寻找
而初始化时,将数组的所有元素都设为-1
所以当寻找到不为-1的数时,就将对应的文件描述符 添加到rfds读集合中

这样就可以保证在查询时,可以寻找到历史的文件描述符


HandlerEvent函数——处理就绪事件

继续遍历fdarray数组,跳过数组元素为-1的

若为合法文件描述符 则有两种情况:
listensock套接字 或者 普通文件描述符


第一种情况

是listensock套接字 并且在rfds集合中
为了方便观看,所以写了一个Accepter函数用于获取新连接的动作

Accepter函数

此时就可以直接使用accept函数了,定义一个客户端IP和客户端端口号
用于获取客户端IP和端口号

定义sock 用于接收返回值,当返回值sock大于0时,即获取连接成功

因为下标为0处,已经被listensock套接字占用了,所以从下标为1位置开始
找到数组元素为-1(表示该位置没有被使用)
将返回值sock赋值给对应的数组元素


第二种情况

不是listensock套接字 但在rfds集合中 即普通文件描述符

使用recv函数 ,将文件描述符fd中的数据 发送到 buffer中

若返回值s大于0,则表示返回成功
在buffer数据的基础上,添加 select server echo ,通过 send 函数 重新发送给文件描述符fd中,被select管理

返回值的其他情况,都是打印信息
最后关闭文件

完整代码

SelectServer.hpp
//SelectServer  V1版本
#include<iostream>
#include<string>
#include<sys/select.h>
#include<cstring>
#include"Sock.hpp"
#include"Log.hpp"
#include"Err.hpp"
using namespace std;


const static int gport=8888;    

typedef int type_t;//定义一个整形数组

class SelectServer
{
  static const int N=sizeof(fd_set)*8;//N对应位图大小

 public:
      SelectServer(uint16_t port=gport)
      :port_(port)
      {}

      void InitServer()//初始化
      {
           listensock_.Socket();//创建套接字
           listensock_.Bind(port_);//绑定
           listensock_.Listen();//设置监听状态
           
           //对fdarray数组进行初始化
           for(int i=0;i<N;i++)
           {
             fdarray_[i]= defaultfd;
           }
      }


     void Accepter()//获取新连接的动作
     {
             //这里再使用accept 就不会阻塞了
             //listen套接字底层一定有就绪的事件 即连接已经到来了
             string clientip;
             uint16_t  clientport;
            int sock=listensock_.Accept(&clientip,&clientport);//获取客户端IP和端口号
            if(sock<0)
            {
              return;
            }
            
            //当得到对应新连接的sock套接字,是不能进行read/recv
            //并不知道sock上的数据是否就绪的
            //所以需要将sock交给select,由select进行管理
            logMessage(Debug,"[%s:%d],sock:%d",clientip.c_str(),clientport,sock );
             //只需把新获取的sock 添加到 数组中
             int pos=1;
             for(;pos<N;pos++)
             {
                if(fdarray_[pos]==defaultfd)//说明没有被占用
                {
                   break;
                }
             }
             if(pos>=N)//整个数组中的位置全被占用了
             {
                close(sock);
                logMessage(Warning,"sockfd[] array full");
             }
             else //找到了对应的位置
             {
                fdarray_[pos]=sock;
             }
     }
     
      void  HandlerEvent(fd_set  &rfds)//处理就绪事件
      { 
             for(int i=0;i<N;i++)
             {
               if(fdarray_[i]==defaultfd)
               {
                  continue;
               }
               //合法fd

               //若套接字为listensock套接字
               if(fdarray_[i]==listensock_.Fd() &&FD_ISSET(listensock_.Fd(),&rfds))
               {
                  Accepter();
               }
               //若套接字不是listensock套接字,但在rfds集合中
                else if ((fdarray_[i] != listensock_.Fd()) && FD_ISSET(fdarray_[i], &rfds)) 
               {
                   //普通文件描述符就绪
                   int fd=fdarray_[i];
                   char buffer[1024];
                   ssize_t s=recv(fd,buffer,sizeof(buffer)-1,0);
                   //读取不会被阻塞
                   if(s>0)//读取成功
                   {
                     buffer[s-1]=0;
                     cout<<"client# "<<buffer<<endl;

                     //发送回去 也要被select管理
                      string echo=buffer ;
                      echo+= "[select server echo ]";
                      send(fd,echo.c_str(),echo.size(),0);//发送消息 将echo内的数据 交给fd
                   }
                   else 
                   {
                     if(s==0)//读到文件结尾
                     {
                       logMessage(Info,"client quit...,fdarray_[i] -> defaultfd:%d->%d",fd,defaultfd);
                     }
                     else //读取失败 
                     {
                       logMessage(Warning,"recv error,client quit...,fdarray_[i] -> defaultfd:%d->%d",fd,defaultfd);
                     }  
                      close(fdarray_[i]);
                      fdarray_[i]=defaultfd;
                   }   
                } 
             } 
         
      }
      

        void DebugPrint()
       {
         cout<<"fdarray_[]:"<<endl;
         for(int i=0;i<N;i++)
         {
          if(fdarray_[i]==defaultfd)
          {
            continue;
          }
          cout<<fdarray_[i]<<" ";
         }
         cout<<"\n";
       }

      void Start() //启动
      {
        //在网络中,新连接到来被当作 读事件就绪
        //对应不同的事件就绪,做出不同的动作
        
        fdarray_[0]=listensock_.Fd();//先将listensock套接字添加到数组中
        while(true) 
        {
          //因为rfds是输入 输出型参数,就注定了每次都要对rfds进行重置
          //重置 就通过 fdarray数组知道 历史上有那些fd

          //因为服务器在运行中,sockfd的值一直在动态变化,所以maxfd也一直在变化
          //maxfd也要动态更新
            fd_set rfds;//作为读文件描述符集合
            FD_ZERO(&rfds);//将集合清空
            int maxfd=fdarray_[0];
            for(int i=0;i<N;i++)
            {
               if(fdarray_[i]==defaultfd)
               {
                 continue;
               }
               //将合法fd添加到rfds集合中
               FD_SET(fdarray_[i],&rfds);
               if( maxfd<fdarray_[i])
               {
                  maxfd=fdarray_[i];
               }
            }


            int n=select(  maxfd+1,&rfds,nullptr,nullptr,nullptr);//将listensock套接字添加到读文件描述符集合中
            //timeout 设为nullptr后,全部为阻塞等待

            switch(n)
            {
              case 0:   //表示没有任何一个文件描述符就绪 
               logMessage(Debug,"timeout,%d: %s",errno,strerror(errno));
               break;

             case -1:  //等待失败 返回-1
              logMessage(Warning,"%d: %s",errno,strerror(errno));
              break;

             default:  //大于0 ,则表示成功 返回有多少文件描述符就绪
                logMessage(Debug,"有一个就绪事件发生了:%d",n);
                 HandlerEvent(rfds);//处理就绪事件
                 DebugPrint();//打印数组内容
               break;
            }
        }
      }
       
      ~SelectServer()
       {
        listensock_.Close();
       }

 private:
     uint16_t port_;//端口号
      Sock listensock_;//创建Sock对象
      type_t fdarray_[N];//自己定义一个数组,与位图大小相同,来进行已经获得的sock进行管理
};

Err.hpp(错误信息)
#pragma once 

enum
{
  USAGE_ERR=1,
  SOCKET_ERR,//2
  BIND_ERR,//3
  LISTEN_ERR,//4
  SETSID_ERR,//5
  OPEN_ERR//6
};





Log.hpp(日志打印)
#pragma once 
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cstring>
#include<cstdarg>
#include<unistd.h>
#include<sys/types.h>
#include<time.h>

const std::string  filename="tecpserver.log";

//日志等级
enum{
 Debug=0, // 用于调试
 Info  ,  //1 常规
 Warning, //2 告警
 Error ,  //3  一般错误
 Tatal ,  //4 致命错误
 Uknown//未知错误
};

static  std::string tolevelstring(int level)//将数字转化为字符串
{
  switch(level)
  {
     case  Debug : return "Debug";
     case Info : return "Info";
     case Warning : return "Warning";
     case  Error : return "Error";
     case Tatal : return "Tatal";
     default: return "Uknown";
  }
}
std::string gettime()//获取时间
{
   time_t curr=time(nullptr);//获取time_t
   struct tm *tmp=localtime(&curr);//将time_t 转换为 struct tm结构体
   char buffer[128];
   snprintf(buffer,sizeof(buffer),"%d-%d-%d %d:%d:%d",tmp->tm_year+1900,tmp->tm_mon+1,tmp->tm_mday,
   tmp->tm_hour,tmp->tm_min,tmp->tm_sec);
   return buffer;

}
void logMessage(int level, const char*format,...)
{
   //日志左边部分的实现
   char logLeft[1024];
   std::string level_string=tolevelstring(level);
   std::string curr_time=gettime();
   snprintf(logLeft,sizeof(logLeft),"%s %s %d",level_string.c_str(),curr_time.c_str());

   //日志右边部分的实现
   char logRight[1024]; 
   va_list p;//p可以看作是1字节的指针
   va_start(p,format);//将p指向最开始
   vsnprintf(logRight,sizeof(logRight),format,p);
   va_end(p);//将指针置空
   
   //打印日志 
   printf("%s%s\n",logLeft,logRight);

   //保存到文件中
   FILE*fp=fopen( filename.c_str(),"a");//以追加的方式 将filename文件打开
   //fopen打开失败 返回空指针
   if(fp==nullptr)
   {
      return;
   }
   fprintf(fp,"%s%s\n",logLeft,logRight);//将对应的信息格式化到流中
   fflush(fp);//刷新缓冲区
   fclose(fp);
}



main.cc(主函数调用)
#include"SelectServer.hpp"
#include<memory>
int main()
{
    std::unique_ptr<SelectServer> svr(new SelectServer());
    svr->InitServer();//初始化
    svr->Start();//启动
    return 0;
}




makefile
SelectServer:main.cc
	g++ -o $@ $^ -std=c++11
	
.PHONY:clean
clean:
	rm -f SelectServer
Sock.hpp(套接字接口实现)


#include<iostream>
#include<cstring>
#include<cstdlib>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
#include"Log.hpp"
#include"Err.hpp"

static const int  gbacklog=32;
static const int defaultfd=-1;
class Sock
{
 public:
 Sock() //构造
 :_sock(defaultfd)
 {
 }

 void  Socket()//创建套接字
 {
  _sock=socket(AF_INET,SOCK_STREAM,0);
  if(_sock<0)//套接字创建失败
  {
    logMessage( Tatal,"socket error,code:%s,errstring:%s",errno,strerror(errno));
    exit(SOCKET_ERR);
  }
 }

  void Bind(uint16_t port)//绑定
  {
   struct sockaddr_in local;
   memset(&local,0,sizeof(local));//清空
   local.sin_family=AF_INET;//16位地址类型
   local.sin_port= htons(port); //端口号
   local.sin_addr.s_addr= INADDR_ANY;//IP地址
   
   //若小于0,则绑定失败
   if(bind(_sock,(struct sockaddr*)&local,sizeof(local))<0)
   {
      logMessage( Tatal,"bind error,code:%s,errstring:%s",errno,strerror(errno));
      exit(BIND_ERR);
   }
  }
   
   void Listen()//将套接字设置为监听状态
   {
      //小于0则监听失败
      if(listen(_sock,gbacklog)<0)
      {
        logMessage( Tatal,"listen error,code:%s,errstring:%s",errno,strerror(errno));
        exit(LISTEN_ERR);
      }
   }

   int Accept(std::string *clientip,uint16_t * clientport)//获取连接
   {
        struct sockaddr_in temp;
        socklen_t len=sizeof(temp);
        int sock=accept(_sock,(struct sockaddr*)&temp,&len);

        if(sock<0)
        {
             logMessage(Warning,"accept error,code:%s,errstring:%s",errno,strerror(errno));
        }
        else 
        {
            //inet_ntoa 4字节风格IP转化为字符串风格IP
            *clientip = inet_ntoa(temp.sin_addr) ; //客户端IP地址
            //ntohs 网络序列转主机序列
            *clientport= ntohs(temp.sin_port);//客户端的端口号
            

        }
        return sock;//返回新获取的套接字
   }

   int Connect(const std::string&serverip,const uint16_t &serverport )//发起链接
   {
      struct sockaddr_in server;
      memset(&server,0,sizeof(server));//清空
      server.sin_family=AF_INET;//16位地址类型
      server.sin_port=htons(serverport);//端口号
      //inet_addr  字符串风格IP转化为4字节风格IP
      server.sin_addr.s_addr=inet_addr(serverip.c_str());//IP地址
      //成功返回0,失败返回-1
      return  connect(_sock, (struct sockaddr*)&server,sizeof(server));
    
    }

    int Fd()
    {
      return _sock;
    }
    void Close()
    {
      if(_sock!=defaultfd)
     {
       close(_sock);
     }

    }
    
 ~Sock()//析构
 {
    
 }
 private:
 int _sock;

};


SelectServer_v2

相比于V1版本,V2版本的 type_t 类型从 int整形 变为 结构体


由于数组的每一个元素都是结构体,所以就需要都每一个成员进行初始化


同时分别通过第一个、第二个、第三个比特位 来表示读事件、写事件、异常事件

start的改写

由于数组的类型是一个结构体,对应的下标为0位置处的元素 也是要说明 结构体对应的成员

将下标为0位置处的元素 (lisetnsock套接字) 设置为读事件

在循环中,设置rfds(读集合)、wfds(写集合)
分别将两者清空


同样与V1相同,需要设置maxfd,用于slect的第一个参数

还需判断,当前的数组元素 是读集合还是写集合
最终将两者添加到select函数中
由于没有异常集合,所以设置为nullptr


HandlerEvent函数的改写

相比于V1,多了写事件,所以参数也应该多传一个


由于有读事件和写事件,所以 if 判断 else if 判断 区分

若为读事件,并且数组的元素 在rfds读集合中
则进入 if 判断
进行V1操作 即判断是 listensock套接字 还是 普通文件描述符
这里为了方便观看,所以把 普通文件描述符代码放入 Recver中


若为写事件,并且数组的元素在wfds写集合中
则进入else if 判断


完整代码

#include <iostream>
#include <string>
#include <sys/select.h>
#include <cstring>
#include "Sock.hpp"
#include "Log.hpp"
#include "Err.hpp"
using namespace std;

const static int gport = 8888;
const static int defaultevent=0;

// 通过第一个 第二个 第三个比特位 来表示 读 写 异常
#define READ_EVENT (0x1)        // 读事件
#define WRITE_EVENT (0x1 << 1)  // 写事件
#define EXCEPT_EVENT (0x1 << 2) // 异常事件

typedef struct FdEvent
{
  int fd;
  uint8_t event;       // 表示关心这个文件描述符上的什么事件
  string clientip;    // 该文件描述符是那个客户端ip地址
  uint16_t clientport; 该文件描述符是那个客户端的端口号
} type_t;

class SelectServer
{ 
  static const int N = sizeof(fd_set) * 8; // N对应位图大小

public:
  SelectServer(uint16_t port = gport)
      : port_(port)
  {
  }

  void InitServer() // 初始化
  {
    listensock_.Socket();    // 创建套接字
    listensock_.Bind(port_); // 绑定
    listensock_.Listen();    // 设置监听状态

    // 对fdarray数组进行初始化
    for (int i = 0; i < N; i++)
    {
      fdarray_[i].fd = defaultfd;
      fdarray_[i].event = defaultevent; // 默认为0 表示一个事件都不关心
      fdarray_[i].clientport = 0;
    }
  }

  void Accepter() // 获取新连接的动作
  {
    // 这里再使用accept 就不会阻塞了
    // listen套接字底层一定有就绪的事件 即连接已经到来了
    string clientip;
    uint16_t clientport;
    int sock = listensock_.Accept(&clientip, &clientport); // 获取客户端IP和端口号
    if (sock < 0)
    {
      return;
    }

    // 当得到对应新连接的sock套接字,是不能进行read/recv
    // 并不知道sock上的数据是否就绪的
    // 所以需要将sock交给select,由select进行管理
    logMessage(Debug, "[%s:%d],sock:%d", clientip.c_str(), clientport, sock);
    // 只需把新获取的sock 添加到 数组中
    int pos = 1;
    for (; pos < N; pos++)
    {
      if (fdarray_[pos].fd == defaultfd) // 说明没有被占用
      {
        break;
      }
    }
    if (pos >= N) // 整个数组中的位置全被占用了
    {
      close(sock);
      logMessage(Warning, "sockfd[] array full");
    }
    else // 找到了对应的位置
    {
      fdarray_[pos].fd = sock;
      fdarray_[pos].event= READ_EVENT;
      fdarray_[pos].clientip=clientip;
      fdarray_[pos].clientport=clientport;

    }
  }

  void Recver(int index)
  {
    // 普通文件描述符就绪
    int fd = fdarray_[index].fd;
    char buffer[1024];
    ssize_t s = recv(fd, buffer, sizeof(buffer) - 1, 0);
    // 读取不会被阻塞
    if (s > 0) // 读取成功
    {
      buffer[s - 1] = 0;
      cout <<fdarray_[index].clientip <<":" <<fdarray_[index].clientport<< buffer << endl;

      // 发送回去 也要被select管理
      string echo = buffer;
      echo += "[select server echo ]";
      send(fd, echo.c_str(), echo.size(), 0); // 发送消息 将echo内的数据 交给fd
    }
    else
    {
      if (s == 0) // 读到文件结尾
      {
        logMessage(Info, "client quit...,fdarray_[i] -> defaultfd:%d->%d", fd, defaultfd);
      }
      else // 读取失败
      {
        logMessage(Warning, "recv error,client quit...,fdarray_[i] -> defaultfd:%d->%d", fd, defaultfd);
      }
      close(fdarray_[index].fd);
      fdarray_[index].fd = defaultfd;
      fdarray_[index].event=defaultevent;
      fdarray_[index].clientip.resize(0);
      fdarray_[index].clientport=0;
    }
  }

      
  void HandlerEvent(fd_set &rfds, fd_set &wfds) // 处理就绪事件
  {
    for (int i = 0; i < N; i++)
    {
      if (fdarray_[i].fd == defaultfd)
      {
        continue;
      }
      // 合法fd

      // 若为读事件,并且读事件已经在rfds集合中
      if ((fdarray_[i].event & READ_EVENT) && FD_ISSET(fdarray_[i].fd, &rfds))
      {
        // 处理读取
        //  1.accept  2.recv
     
        // 若套接字为listensock套接字
        if (fdarray_[i].fd == listensock_.Fd())
        {
          Accepter();
        }
        // 若套接字不是listensock套接字,但在rfds集合中
        else if ((fdarray_[i].fd != listensock_.Fd()) )
        {
          Recver(i);
        }
        else 
        {}
      }
      //若为写事件,并且写事件已经在rfds集合中
      else  if ((fdarray_[i].event & WRITE_EVENT) && FD_ISSET(fdarray_[i].fd, &wfds))
      {
              
      }
      else 
      {
        //异常事件
      }
    }
  }
  

  void DebugPrint()
  {
    cout << "fdarray_[]:" << endl;
    for (int i = 0; i < N; i++)
    {
      if (fdarray_[i].fd == defaultfd)
      {
        continue;
      }
      cout << fdarray_[i].fd << " ";
    }
    cout << "\n";
  }



  void Start() // 启动
  {
    // 在网络中,新连接到来被当作 读事件就绪
    // 对应不同的事件就绪,做出不同的动作

    fdarray_[0].fd = listensock_.Fd(); // 先将listensock套接字添加到数组中
    fdarray_[0].event = READ_EVENT;    // 设置为 读事件

    while (true)
    {
      // 因为rfds是输入 输出型参数,就注定了每次都要对rfds进行重置
      // 重置 就通过 fdarray数组知道 历史上有那些fd

      // 因为服务器在运行中,sockfd的值一直在动态变化,所以maxfd也一直在变化
      // maxfd也要动态更新
      fd_set rfds;    // 作为读文件描述符集合
      fd_set wfds;    // 作为写文件描述符集合
      FD_ZERO(&rfds); // 将读集合清空
      FD_ZERO(&wfds); // 将写集合清空

      int maxfd = fdarray_[0].fd;
      for (int i = 0; i < N; i++)
      {
        if (fdarray_[i].fd == defaultfd)
        {
          continue;
        }
        // 将合法fd添加到rfds集合中

        if (fdarray_[i].event & READ_EVENT) // 若当前为读,则添加到读集合中
        {
          FD_SET(fdarray_[i].fd, &rfds);
        }
        if (fdarray_[i].event & WRITE_EVENT) // 若当前为写,则添加到写集合中
        {
          FD_SET(fdarray_[i].fd, &wfds);
        }
        if (maxfd < fdarray_[i].fd)
        {
          maxfd = fdarray_[i].fd;
        }
      }

      int n = select(maxfd + 1, &rfds, &wfds, nullptr, nullptr); // 将listensock套接字添加到读文件描述符集合中
      // timeout 设为nullptr后,全部为阻塞等待

      switch (n)
      {
      case 0: // 表示没有任何一个文件描述符就绪
        logMessage(Debug, "timeout,%d: %s", errno, strerror(errno));
        break;

      case -1: // 等待失败 返回-1
        logMessage(Warning, "%d: %s", errno, strerror(errno));
        break;

      default: // 大于0 ,则表示成功 返回有多少文件描述符就绪
        logMessage(Debug, "有一个就绪事件发生了:%d", n);
        HandlerEvent(rfds, wfds); // 处理就绪事件
        DebugPrint();             // 打印数组内容
        break;
      }
    }
  }
  
private:
  uint16_t port_;     // 端口号
  Sock listensock_;   // 创建Sock对象
  type_t fdarray_[N]; // 自己定义一个数组,与位图大小相同,来进行已经获得的sock进行管理
};

3. select 特点

  • 文件描述符是有上限的,云服务器是1024

  • 需要使用一个array数组保存slect中的文件描述符 (start最终版本有详细说)


4. select 缺点

  • 每次调用select时,都需要手动设置fd集合
    (如:每次都需要设置一个maxfd, 通过不断判断后 作为第一个参数)
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,在fd很多时开销会很大
    (每次都需要用户需要告诉内核,那些文件描述符的那些事件需要关心)
  • 每次调用select,都需要在内核遍历传递过来的所有fd,在fd很多时 开销会很大
    (每次内核都需要告诉用户,那些关心的文件描述符的那些事件就绪)
  • select支持的文件描述符太小
    (如:V1中设置fdarray数组的类型为int,大小为固定长度N)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1061199.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【成像光敏描记图提取和处理】成像-光电容积描记-提取-脉搏率-估计(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Apollo Planning2.0决策规划算法代码详细解析 (2): vscode gdb单步调试环境搭建

前言: apollo planning2.0 在新版本中在降低学习和二次开发成本上进行了一些重要的优化,重要的优化有接口优化、task插件化、配置参数改造等。 GNU symbolic debugger,简称「GDB 调试器」,是 Linux 平台下最常用的一款程序调试器。GDB 编译器通常以 gdb 命令的形式在终端…

C/C++进程超详细详解【下部分】(系统性学习day8)

目录 前言 一&#xff0c;有名管道通信 1 .概念 2 .创建有名管道 实例代码如下&#xff1a; 二、信号通信 1 .概念 2 .用户进程对信号的响应方式 3. 用户进程对常用信号的缺省操作 4. 信号处理流程 5. 信号相关函数(系统调用) 5.1 kill - 给指定进程发送信号 实例代…

使用云服务器部署SpringBoot+Vue项目

一、购买云服务器并配置安全组 二、准备好前后端项目并先打包好 对于前端文件。新建文件 .env.development VUE_APP_BASEURLhttp://localhost:9090 还有新建文件 .env.production VUE_APP_BASEURLhttp://:9090 main.js 设置全局变量 $baseUrl Vue.prototype.$baseUrlproc…

mysql面试题18:MySQL中为什么要用 B+树,为什么不用二叉树?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:MySQL中为什么要用 B+树,为什么不用二叉树? MySQL数据库索引是一种数据结构,用于提高数据查询的效率。在MySQL中,常用的索引类型包括B+树索引…

LLMs 奖励模型 RLHF: Reward model

在这个阶段&#xff0c;您已经拥有了训练奖励模型所需的一切。虽然到目前为止&#xff0c;已经付出了相当多的人力&#xff0c;但在训练奖励模型完成后&#xff0c;您将不需要再涉及更多的人类。相反&#xff0c;奖励模型将在强化学习微调过程中代替人类标记者&#xff0c;自动…

(五)正点原子STM32MP135移植——烧录

一、概述 这里用的烧录方式是使用STM32CubeProgrammer USB方式烧录 二、文件准备 还记得FIP_artifacts文件夹吗&#xff0c;里面存放了TF-A、optee、u-boot编译输出的东西&#xff0c;以及最后的fip-stm32mp135-atk-optee.bin文件 烧写程序需要准备这些&#xff1a; 1. …

网站强制跳转至国家反诈中心该怎么办?怎么处理?如何解封?

在互联网环境中&#xff0c;网站安全是非常重要的。然而&#xff0c;在实际操作过程中&#xff0c;不少网站可能因内容问题、技术安全漏洞等原因被迫下线甚至跳转至国家反诈骗中心网址。面对这一严峻问题&#xff0c;我们如何有效解决&#xff0c;让网站恢复运行并解除强制跳转…

LLMs 从人类获得反馈RLHF: Obtaining feedback from humans

在使用RLHF进行微调的第一步是选择要使用的模型&#xff0c;并使用它准备一个人工反馈数据集。 您选择的模型应该具备执行您感兴趣的任务的一定能力&#xff0c;无论这是文本摘要、问答还是其他任务。通常情况下&#xff0c;您可能会发现&#xff0c;从已经在许多任务上进行了…

<学习笔记>从零开始自学Python-之-常用库篇(十二)Matplotlib

Matplotlib 是Python中类似 MATLAB的绘图工具&#xff0c;Matplotlib是Python中最常用的可视化工具之一&#xff0c;可以非常方便地创建2D图表和一些基本的3D图表&#xff0c;可根据数据集&#xff08;DataFrame&#xff0c;Series&#xff09;自行定义x,y轴&#xff0c;绘制图…

UGUI交互组件Toggle

一.Toggle对象的构造 Toggle和Button类似&#xff0c;是交互组件的一种 如果所示&#xff0c;通过菜单创建了两个Toggle&#xff0c;Toggle2中更换了背景和标记资源 对象说明Toggle含有Toggle组件的对象Background开关背景Checkmark开关选中标记Label名称文本 二.Toggle组件属…

基于Spring Boot的中小型医院网站的设计与实现

目录 前言 一、技术栈 二、系统功能介绍 前台首页界面 用户登录界面 用户注册界面 门诊信息详情界面 预约挂号界面 药品详情界面 体检报告界面 管理员登录界面 用户管理界面 医师管理界面 科室类型管理界面 门诊信息管理界面 药库信息管理界面 预约挂号管理界面…

计算机网络基础(一):网络系统概述、OSI七层模型、TCP/IP协议及数据传输

通信&#xff0c;在古代是通过书信与他人互通信息的意思。 今天&#xff0c;“通信”这个词的外沿已经得到了极大扩展&#xff0c;它目前的大意是指双方或多方借助某种媒介实现信息互通的行为。 如果按照当代汉语的方式理解“通信”&#xff0c;那么古代的互遣使节、飞鸽传书…

番外12:连续类功率放大器理论-连续类实现带宽拓展的底层原理

连续类功放通解&#xff1a;连续类功率放大器理论-连续类实现带宽拓展的底层原理-基础 本次内容理论性较强&#xff0c;适合对功率放大器理论研究比较感兴趣以及想发论文的小朋友&#xff0c;着重探讨现有的一些带宽拓展模式&#xff08;也就是连续类&#xff09;的基本实现原…

MATLAB算法实战应用案例精讲-【优化算法】火烈鸟搜索优化算法(FSA)(附python代码实现)

前言 火烈鸟搜索算法(flamingo search algorithm,fsa)是一种模拟火烈鸟群体觅食行为的新型智能优化算法,可以用于路径规划领域。根据fsa的寻优过程可知,fsa存在以下不足:(1)初始化种群位置是随机的,不能保证种群质量;(2)在个体的迭代更新过程中缺少变异机制,导致种群多…

XFTP上传文件状态出现错误的原因和解决方案

这几天有时候会出现XFTP会出现上传的时候状态出现错误的情况&#xff0c;我没那么在意&#xff0c;但是今天要传比较重要的东西&#xff0c;结果没办法传&#xff0c;我参考了这个方法&#xff0c;但是感觉修改用户组的权限是正确的可能解释的没那准确 之后我是直接把XFTP的登陆…

【LeetCode热题100】--74.搜索二维矩阵

74.搜索二维矩阵 按行搜索&#xff0c;使用二分查找 class Solution {public boolean searchMatrix(int[][] matrix, int target) {for(int[] row : matrix){int index search(row,target);if(index > 0){return true;}}return false;}public int search(int[] nums,int t…

【C++入门到精通】C++入门 —— AVL 树(自平衡二叉搜索树)

阅读导航 前言一、AVL树的概念二、AVL树节点的定义三、AVL树的插入四、AVL树的旋转&#xff08;重点&#xff09;1. 右单旋&#xff08;新节点插入较高左子树的左侧&#xff09;2. 左单旋&#xff08;新节点插入较高右子树的右侧&#xff09;3. 先左单旋再右单旋&#xff08;新…

Kafka安装记录

目录 安装依赖 安装zookeeper 可能遇到的报错 下载安装包 解压 修改配置 其他相关配置 修改日志的位置 修改Zookeeper 启动 测试 创建主题 查看主题 插入数据 查看数据量 消费数据 删除主题 安装依赖 由于Kafka是用Scala语言开发的&#xff0c;运行在JVM上&am…

Elasticsearch安装访问

Elasticsearch 是一个开源的、基于 Lucene 的分布式搜索和分析引擎&#xff0c;设计用于云计算环境中&#xff0c;能够实现实时的、可扩展的搜索、分析和探索全文和结构化数据。它具有高度的可扩展性&#xff0c;可以在短时间内搜索和分析大量数据。 Elasticsearch 不仅仅是一个…