【计算机网络】select 转载

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

文章目录
  • 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
#include
#include
#include
#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进行管理
};

  • 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
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
Err.hpp(错误信息)
#pragma once 

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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
Log.hpp(日志打印)
#pragma once 
#include
#include
#include
#include
#include
#include
#include
#include

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);
}

  • 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
  • 72
  • 73
  • 74
  • 75
  • 76
main.cc(主函数调用)
#include"SelectServer.hpp"
#include
int main()
{
    std::unique_ptr<SelectServer> svr(new SelectServer());
    svr->InitServer();//初始化
    svr->Start();//启动
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
makefile
SelectServer:main.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
rm -f SelectServer

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
Sock.hpp(套接字接口实现)

#include
#include
#include
#include
#include
#include
#include
#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;

};

  • 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
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116

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 
#include 
#include 
#include 
#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进行管理
};

  • 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
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255

3. select 特点

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

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


4. select 缺点

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

文章知识点与官方知识档案匹配,可进一步学习相关知识
网络技能树认识身边的计算机网络常见的网络设备 38742 人正在系统学习中
阅读终点,创作起航,您可以撰写心得或摘录文章要点写篇博文。 去创作
【计算机网络】select 转载_第1张图片 风起、风落
  • 51
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 90
    评论
IO——test
07-27
NULL 博文链接:https://ladybird2010.iteye.com/blog/736909
网络 io使用实例 select,poll,epoll
03-08
网络 io的使用实例 1.阻塞 2.多线程 3. select 4.poll, 5.epoll
IO多路复用之 select——完整代码
02-10
三种 IO多路复用机制: 一: select 二:poll 三:epoll

以上三种IO多路复用的完整代码,皆可以在我的资源列表中获取下载:
资源列表:http://download.csdn.net/user/qiulanzhu


总结网络 IO模型与 select模型的Python实例讲解
09-21
同步、异步、阻塞、非阻塞,当这些网络 IO名词堆到一起时难免使编程初学者感到困惑,这里我们就来为大家总结网络 IO模型与 select模型的Python实例讲解:
学习C++项目—— select模型,poll模型和epoll模型
墨心
11-28 1303
学习 计算机网络编程 一、思路和学习方法   本文学习于:C语言技术网(www.freecplus.net),在 b 站学习于 C 语言技术网,并加以自己的一些理解和复现,如有侵权会删除。   接下来应该是网络编程部分最难也是最常用的部分,同时在这一章我会全部学习完毕。 二、网络编程继续深入 2.1 I/O复用   多进程/多线程网络服务端在创建进程/线程CPU和内存开销很大。多线程/多进程并发模型,为每个socket分配一个进程/线程。I/O多路复用,采用单个进/线程就可以管理多个socket。I/O复用有
计算机网络—— select、poll、epoll底层原理
庄小焱
11-07 1872
本博文主要是介绍 select、poll、epoll相关原理。帮助大家在linux的网络优化与网络模型选择提供一个思路。
如何设置TCP参数,提升系统性能
闪电小子
06-11 1698
前言

TCP 性能的提升不仅考察 TCP 的理论知识,还考察了对于操作系统提供的内核参数的理解与应用。

TCP 协议是由操作系统实现,所以操作系统提供了不少调节 TCP 的参数。

Linux TCP 参数

如何正确有效的使用这些参数,来提高 TCP 性能是一个不那么简单事情。我们需要针对 TCP 每个阶段的问题来对症下药,而不是病急乱投医。

接下来,将以三个角度来阐述提升 TCP 的策略,分别是:

TCP 三次握手的性能提升;


TCP 四次挥手的性能提升;


TCP 数
计算机网络协议(三)——UDP、TCP、Socket
热门推荐
ghw15221836342的博客
09-04 4万+
底层网络知识详解:最重要的传输层概述一、UDP协议二、TCP协议2.1 TCP的三次握手 概述

这个专栏的计算机网络协议,我是在极客时间上学习 已经有三万多人购买的刘超老师的趣谈网络协议专栏,讲的特别好,像看小说一样学习到了平时很枯燥的知识点,计算机网络的书籍太枯燥,感兴趣的同学可以去付费购买,绝对物超所值,本文就是对自己学习专栏的总结,评论区可以留下你的问题,咱们一起讨论!

传输层中有两…


计算机基础知识—— 计算机网络
Zuoerfeng
11-07 2681
(1) 建立TCP服务器的各个系统调用 建立一个TCP服务器需要涉及到以下的几个系统调用:

socket():开启一个套接字
bind():绑定IP地址和端口号,注意绑定的时候,服务器一般是要主动绑定IP地址和端口号的,但是客户端一般是不需要的,因为客户端发送连接的时候,内核会自动分配一个端口号给它。
listen():监听是否有客户端请求
accept():阻塞进程,等待客户端的接入,接入之后…


大型ERP系统物流CRM详细设计说明书.docx
10-07
大型ERP系统物流CRM详细设计说明书
执行力专业培训课程PPT模板.pptx
10-07
执行力专业培训课程PPT模板.pptx
Logistic回归模型.pdf
10-07
Logistic回归模型.pdf
最新素材ppt模板下载.pptx
10-07
最新素材ppt模板下载.pptx
UniApp HelloWorld程序入门必知
10-07
基础实战项目
(PC+WAP)蔬菜水果基地网站源码 蔬菜水果配送类网站pbootcms模板
10-07
(PC+WAP)蔬菜水果配送类网站pbootcms模板 蔬菜水果基地网站源码下载 PbootCMS内核开发的网站模板,该模板适用于蔬菜水果网站、果蔬基地网站等企业,当然其他行业也可以做,只需要把文字图片换成其他行业的即可; PC+WAP,同一个后台,数据即时同步,简单适用!附带测试数据!

友好的seo,所有页面均都能完全自定义标题/关键词/描述,PHP程序(php≥7.0,<8.0),安全、稳定、快速;用低成本获取源源不断订单!

后台:域名/admin.php
账号:admin
密码:admin

模板特点

1:手工书写DIV+CSS、代码精简无冗余。
2:自适应结构,全球先进技术,高端视觉体验。
3:SEO框架布局,栏目及文章页均可独立设置标题/关键词/描述。
4:附带测试数据、安装教程、入门教程、安全及备份教程。
5:后台直接修改联系方式、传真、邮箱、地址等,修改更加方便。


精致闹钟2004V140
10-07
1、该版本默认的[定时关机][自动运行][事件提醒]等项目的最大数目为 [255],基本上可以满足个人使用。

2、对于已经失效的指定日期的定时项目系统不予自动删除,请您自己在
设置中手工删除已经无用的定时项目。

3、一个定时项目只在同一天内提醒一次,提醒过后,即使您将时间改到
提醒的时刻,系统也不会再次提醒,只有等到下一天,或者您用编辑模式
打开该定时项目,直接确定返回,可以重新开启该项目本天的提醒功能。

4、定时项目只在定时时间有效,延时项目从延时开始时刻有效。

5、对于延时操作,如果开机时没有自动运行程序,那么在运行程序时,
只有在运行程序之后时刻的延时操作有效。


竞选班干部演讲展示PPT模板.pptx
10-07
竞选班干部演讲展示PPT模板.pptx
国内外数字孪生城市建设的经验及启示_易雪琴.caj
最新发布
10-07
国内外数字孪生城市建设的经验及启示_易雪琴.caj
系统编程多路 io复用 select
07-27

你可能感兴趣的:(计算机网络,数据库,计算机网络)