其他分享
首页 > 其他分享> > 高效IO——IO多路转接select

高效IO——IO多路转接select

作者:互联网

目录

一.概念

二.select函数

        2.1 函数原型

        2.2参数详细介绍

                2.2.1 nfd

                2.2.2readfds,writefds,errorfds

                2.2.3 timeout

        2.3. 网络中读/写/异常的就绪条件

        2.4 select特点

        2.5 select缺点

三.select的使用


一.概念

        IO主要有两个动作,等待条件就绪和进行数据拷贝。高效IO就是将等待时间比重减小。

        IO多路转接是高效IO的一种。通过调用select,poll,epoll在同一时刻等待多个文件描述符。当至少一个文件描述符准备就绪,再来进行IO操作时,就不需要等待了。

        这样一次性等待多个文件描述符,条件就绪的概率增加了,等待的时间也会减少。

        下文主要介绍select,poll,epoll如何实现多路转接。

二.select函数

        2.1 函数原型

#include <sys/select.h>

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

作用:select可以监视多个文件描述符的状态,程序会停在select这里等待,直到一个或者多个文件描述符状态发生变化。

参数:

参数作用
nfd需要监视最大文件描述符值加1
readfds类型为fd_set,可读文件文件描述符的集合,输入输出型参数
writefds类型为fd_set,可写文件文件描述符的集合,输入输出型参数
errorfds类型为fd_set,异常文件文件描述符的集合,输入输出型参数
timeout结构为timeval,用来设置select等待时间,输入输出型参数

返回值:

        2.2参数详细介绍

                2.2.1 nfd

        nfd:需要监视最大文件描述符值加1。

如果需要监视的文件描述符为1,2,3,4,nfd等于5。如果逍遥监视的文件描述符为1,5,nfd等于6。

                2.2.2readfds,writefds,errorfds

        readfds,writefds,errorfds是主要和类型fd_set有关。并且它们是类似的。

关于fd_set结构:

typedef struct
{
/*XPG4.2requiresthismembername.Otherwiseavoidthename
fromtheglobalnamespace.*/
#ifdef__USE_XOPEN
__fd_maskfds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->fds_bits)
#else
__fd_mask__fds_bits[__FD_SETSIZE/__NFDBITS];
#define__FDS_BITS(set)((set)->__fds_bits)
#endif
}fd_set;

        fd_set是文件描述符集,结构实际是一个位图。

        readfds,writefds,errorfds是输入输出参数,输入时是用户想告诉内核需要监视哪些文件描述符,当作为输出时,是内核想告诉用户,那些文件描述符已经就绪。

        位图的对应位代表着要监视的文件描述符,比特位的内容,作为输入时,内容代表需要监视的文件,作为输出时,内容代表那些文件条件已经就绪。

        比如:readfds:拿8位举例,作为输入时,当输入1001 0101时,是用户想告诉内核,需要监视文件描述符等于0,2,4,7的文件的读事件的状态。作为输出时,输出为1000 0001时,是内核想告诉用户,文件描述符为0,7的文件读事件一ing就绪,可以进行读操作。

        内核监视文件的个数是确定的,说明内核监视文件的个数是有限的。内核监视多个文件描述符,采用监视的方法是轮询监视。

由于不同的系统fd_set实现方式可能不同,可能是数组,可能是结构体,所以提供了一组操作fd_set的接口,来对位图进行设置。

void FD_CLR(int fd, fd_set *fdset);  //用来清除fd_set中相关fd的位
int FD_ISSET(int fd, fd_set *fdset); //用来测试fd_set中相关fd的位是否为真
void FD_SET(int fd, fd_set *fdset);  //用来设置fd_set中相关fd的位
void FD_ZERO(fd_set *fdset);         //用来清除fd_set中的全部位,相当于初始化

        注意:用户输入了监视那些为你文件描述符,内核输出条件就绪的文件,一定只会是这些文件描述符里的子集。 

                2.2.3 timeout

        timeout,结构是timeval。用来设置select等待时间。

关于timeval结构:

struct timeval
{
    time_t      tv_sec;     /* seconds 秒*/
    suseconds_t tv_usec;    /* microseconds 微秒*/
};

参数timeout的取值:

  1. NULL:表示select在没有文件条件就绪时,会阻塞等待。
  2. 0:非阻塞等待,不管条件就没就绪都会返回,用于检测监视的文件的状态。
  3. 特定的时间值:等待一段时间,在时间范围内有文件条件就绪,返回,超过时间select返回0。

timeout也是一个输入输出参数。当输入时,用户告诉内核等待时间timeout,当输出时,内核等待完毕,等待时间timeout就为0了。

        注意:在编码时,由于readfds,writefds,errorfds和timeout都是输入输出型参数,当select一次后,重新select时,由于输出时已经改变参数的值,所以需要重新设定readfds,writefds,errorfds和timeout的值。

        2.3. 网络中读/写/异常的就绪条件

读就绪

写就绪

异常就绪:

        2.4 select特点

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

int main(){
  printf("%lu\n",sizeof(fd_set)*8); 

  return 0;
}

不同系统值不同。

这个在下面编码是可以明显观察到。

        2.5 select缺点

三.select的使用

用select编写一个单进程echo服务器。

注意点:

  1. 需要用数组保存所有要监视的文件描述符。该文件描述符包括进行读的套接字文件描述符和连接套接字。
  2. 当没有连接时,accept会阻塞等待,所以连接也需要select等待。
  3. 连接是socket返回的套接字做的,有新连接来时,也是读就绪。
  4. IO读写是accept返回套接字做的,接收缓冲区的字节数大于低水位标记,读就绪。发送缓冲区剩余空间大小,大于低水位标记,写就绪。
  5. 当获取到连接,不是直接进行读写,而是将accept返回值放到数组中,如果直接读写,如果客户端没有发数据,又会阻塞。
  6. 注意fd_set位图需要用FD_ZERO初始化

套接字设定: 

#pragma once 

#include <iostream>
#include <string>
#include <stdlib.h>
#include <unistd.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>

#define BLACKLOG 5
using namespace std;

class Sock{
  public:
  static int Socket(){
    int sock = 0;
    sock = socket(AF_INET, SOCK_STREAM, 0);
    if(sock < 0){
      cerr << "socket error"<<endl;
      exit(1);
    }
    return sock;
  }
  static void Bind(int sock, int port){
    struct sockaddr_in local;
    
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = htonl(INADDR_ANY);
    if(bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){
      cerr << "bind error" <<endl;
      exit(3);
    }
  }
  static void Listen(int sock){
    if(listen(sock, BLACKLOG) < 0){
      cerr << "listen error"<<endl;
      exit(4);
    }

  }
  static int Accept(int lsock){
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);
    return  accept(lsock, (struct sockaddr *)&peer, &len);
  }


};

select服务器主体:

#pragma once 

#include "Sock.hpp"

#define NUM sizeof(fd_set)*8//数组大小=最多能监视文件个数
#define DET_FD -1//数组默认文件描述符 

class SelectServer{
  private:
    int _lsock;//套接字
    int _port;//端口号
    int array[NUM];//保存要监视的文件描述符
  public:
    SelectServer(int lsock = -1, int port = 8080)
    :_lsock(lsock)
    ,_port(port)
    {}
    void InitServer(){
      for(size_t i =  0; i < NUM; i++){
        array[i] = DET_FD;
      }
      _lsock = Sock::Socket();
      //端口复用                                                                                                                                         
      int opt = 1;                                                    
      setsockopt(_lsock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 
      Sock::Bind(_lsock, _port);
      Sock::Listen(_lsock);
      array[0] = _lsock;

    }
    void AddtoArray(int index){
      //找到没有数组没有占用的位置
      size_t i = 0;
      for(; i < NUM; i++){
        if(array[i] == DET_FD){
          break;
        }
      }
      //满了
      if(i >= NUM){
        cout<<"select is full, close fd"<<endl;
        close(index);
      }
      else{
        array[i] = index;
      }
    }
    void Delete(size_t index){
      if(index >= 0 && index < NUM){
        array[index] = DET_FD;
        
      }

    }
    void Handle(int i){
      //IO条件就绪
      char buf[10240];
      ssize_t n = recv(array[i], buf, sizeof(buf), 0);
      if(n > 0){
        buf[n] = 0;
        cout<<buf<<endl;
      }
      else if(n == 0){
        //对端关闭
        cout<<"client close..."<<endl;
        close(array[i]);
        //文件已经关闭,还需要将数组文件描述符删除
        Delete(i);
      }
      else{
        cerr << "read error"<<endl;
        close(array[i]);
        Delete(i);
      }


    }
    void Start(){
      while(1){
        int maxfd = DET_FD;
        //重新设定,需要等待的文件
        fd_set readfds;
        //初始化fd_set
        FD_ZERO(&readfds);
        //找文件描述符,将要监视的fd_set对应位设置为1
        for(size_t i =0; i < NUM; i++){
          if(array[i] == DET_FD){
            continue;
          }
          cout <<array[i];
          FD_SET(array[i], &readfds);
          //找文件描述符最大值
          if(maxfd < array[i]){
            maxfd = array[i];
          }
        }
        cout<<endl;
        //struct timeval timeout = {5, 0};
        //调用 select 等待多个文件
        //阻塞等待
        int fdn = select(maxfd+1, &readfds, nullptr, nullptr, nullptr);
        if(fdn > 0){
          //有文件就绪
          //找哪个文件就绪
          for(size_t i =0; i < NUM; i++){
            if(array[i] != DET_FD && FD_ISSET(array[i] , &readfds)){
              if(array[i] == _lsock){
                //有新连接
                int sock = Sock::Accept(array[i]);
                if(sock >= 0){
                  cout << "get a link...."<<endl;
                  //加入到数组中
                  AddtoArray(sock);
                }
                
              }
              else{
                //进行IO操作
                Handle(i);
              }
            }
          }

        }
		//超时
        else if(fdn == 0){
          cerr << "select timeout..."<<endl;

        }
		//异常
        else{
          cerr <<"fdn:"<<fdn<< "select error"<<endl;
        }


      }
    }

    ~SelectServer(){
      for(size_t i = 0; i < NUM; i++){
        if(array[i] != DET_FD){
          close(array[i]);
        }
      }
    }


};
#include"selectServer.hpp"

void Notice(string str){
  cout<<"Notice\n\t"<<"please enter port"<<endl;
}

int main(int argc, char *argv[]){
  if(argc != 2){
    Notice(argv[0]);
    exit(1);
  }

  SelectServer *sser = new SelectServer(atoi(argv[1]));
  sser->InitServer();
  sser->Start();
  delete sser;

  return 0;
}

标签:文件,set,int,描述符,转接,fd,IO,select
来源: https://blog.csdn.net/weixin_57023347/article/details/120862397