Linux下select详解和简单封装

select函数原型

  • 系统提供select函数来实现多路复用输入输出模型
  • select系统调用是用来让我们的程序监视多个文件描述符的状态变化的。
  • 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。
    下面是select函数定义以及相关函数定义
       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

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

       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的全部位

下面是关于fd_set的定义:
大致说明一下:fd_set实质是一个long int的数组,而每一个文件描述符对应一个比特位。根据代码可以看出,fd_set的大小取决于__FD_SETSIZE(Linux下默认1024)。注意 :1024表示的不是个数而是大小
怎么算的:
共有__fd_mask × (__FD_SETSIZE / __NFDBITS)× 8个比特位 =
__fd_mask ×(__FD_SETSIZE / (8 * (int) sizeof (__fd_mask)))× 8;
约掉就等于__FD_SETSIZE。


 53 /* The fd_set member is required to be an array of longs.  */                  
 54 typedef long int __fd_mask;
 55 
 56 /* Some versions of <linux/posix_types.h> define this macros.  */
 57 #undef  __NFDBITS
 58 /* It's easier to assume 8-bit bytes than to get CHAR_BIT.  */
 59 #define __NFDBITS   (8 * (int) sizeof (__fd_mask))
 60 #define __FD_ELT(d) ((d) / __NFDBITS)
 61 #define __FD_MASK(d)    ((__fd_mask) 1 << ((d) % __NFDBITS))
 62 
 63 /* fd_set for select and pselect.  */
 64 typedef struct
 65   {
 66     /* XPG4.2 requires this member name.  Otherwise avoid the name
 67        from the global namespace.  */
 68 #ifdef __USE_XOPEN
 69     __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
 70 # define __FDS_BITS(set) ((set)->fds_bits)
 71 #else
 72     __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];                            
 73 # define __FDS_BITS(set) ((set)->__fds_bits)
 74 #endif
 75   } fd_set;

  • 参数解释:

  • nfds:参数nfds是需要监视的最大的文件描述符值+1。

  • readfds、writefds、exceptfds:分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集 合及异常文件描述符的集合。默认情况下,最大为1024。

  • 参数timeout为结构timeval,用来设置select()的等待时间。
    在这里插入图片描述

  • 参数timeout取值:

  • NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。

  • 0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。

  • 特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

  • 关于timeval结构:

struct timeval
{
    __time_t tv_sec;  //秒
    __suseconds_t tv_usec;  //微秒
};
  • 函数返回值:
  • 执行成功则返回文件描述词状态已改变的个数。
  • 如果返回0代表在描述词状态改变前已超过timeout时间,没有返回。
  • 当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout的值变成不可预测。
  • 错误值可能为
  • EBADF :文件描述词为无效的或该文件已关闭。
  • EINTR :此调用被信号所中断。
  • EINVAL :参数n 为负值。
  • ENOMEM :核心内存不足。

select执行过程

理解select模型的关键在于理解fd_set,上面我已经说过了。
为方便说明,现在我们假设取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd.
①fd_set set;
②FD_ZERO(&set),则set用位表示是0000 0000;
③若fd=5,执行FD_SET(fd,&set),后set变为0001,0000(第5位置为1) ;
④若再加入fd=2,fd=1,则set变为0001 0011
⑤执行select(6,&set,0,0,0)阻塞等待;
⑥若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000 0011。注意:没有事件发生的fd=5被清空。
所以用户需要继续监视某一个文件描述符,则需要重新添加文件描述符到集合当中去,然后再次调用select。

注意: 对于一个新打开的普通文件的描述符,在文件为空的情况下,select认为该文件描述符是可读的,不是异常事件。

(特点)优点和缺点

特点

  • 可监控的文件描述符个数取决与sizeof(fd_set)的值。可监视的文件描述符大小为 sizeof(fd_set) * 8。
  • 将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd。一是用于在select 返回后,array作为源数据和fd_set进行FD_ISSET判断;二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。

缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便。
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
  • select支持的文件描述符数量太小。

select封装

下面是和tcp协议一起封装的:
select.hpp

#pragma once
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <vector>
#include "tcpsvr.hpp"

class selectSvr
{

    public:
        selectSvr()
        {
            FD_ZERO(&_readfds);
            _max_readfd = -1;
        }

        //添加
        void Add(int fd)
        {
            //添加到集合当中
            FD_SET(fd, &_readfds);
            //更新一下最大的文件描述符
            if (fd > _max_readfd) {
                _max_readfd = fd;
            }
        }

        //删除
        void Delete(int fd)
        {
            //删除
            FD_CLR(fd, &_readfds);
            //更新
            for (int i = _max_readfd; i >= 0; --i) {
                if (!FD_ISSET(i, &_readfds)) {
                    continue;
                }
                _max_readfd = i;
                break;
            }
        }

        //监控
        bool SelectWait(std::vector<TcpSvr>* vec, int usec = 100)
        {
            struct timeval tv;
            tv.tv_sec = 0;
            tv.tv_usec = usec;

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

            //select返回ret大于0
            for (int i = 0; i <= _max_readfd; ++i) {
                if (FD_ISSET(i, &tmp)) {
                    TcpSvr ts;
                    ts.SetFd(i);
                    vec->push_back(ts);
                }
            }
        
            return true;
        }
    private:
        fd_set _readfds;
        //当前想让select监听的最大文件描述符
        int _max_readfd;
};

tcpsvr.hpp

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



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

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

  int GetFd()
  {
      return _sock;
  }

  bool CreateSock()
  {
    _sock = socket(AF_INET, SOCK_STREAM, 6);
    if (_sock < 0) {
      perror("create");
      return false;
    }
    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());
    ssize_t ret = bind(_sock, (struct sockaddr*)&addr, sizeof(addr));
    if (ret < 0) {
      perror("bind");
      return false;
    }
    return true;
  }

  bool Listen(int Backlog)
  {
    ssize_t ret = listen(_sock, Backlog);
    if (ret < 0) {
      perror("listen");
      return false;
    }
    return true;
  }

  bool Accept(TcpSvr& ts, struct sockaddr_in* addr = NULL)
  {
    struct sockaddr_in peeraddr;
    socklen_t len = sizeof(struct sockaddr_in);
   int ret = accept(_sock, (struct sockaddr*)&peeraddr, &len);
   if (ret < 0) {
     perror("accept");
     return false;
   }
   ts._sock = ret;
   if (addr != NULL){
     memcpy(addr, &peeraddr, len);
   }
   return true;
  }

  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(_sock, (struct sockaddr*)&destaddr, sizeof(destaddr));
    if (ret < 0) {
      perror("connect");
      return 0;
    }
    return true;
  }

  bool Send(std::string& buf)
  {

    int sendSize = send(_sock, buf.c_str(), buf.size(), 0);
    if (sendSize < 0) {
      perror("send");
      return false;
    }
    return true;
  }

  bool Recv(std::string& buffer)
  {
    char buf[1024] = {0};
    //0:阻塞接收
    //MSG_PEEK:探测接收
    int recvSize = recv(_sock, buf, sizeof(buf) - 1, 0);
    if (recvSize < 0) {
      perror("recv");
    }
    else if (recvSize == 0) {
      //如果recvSize等于0,表示对端将连接关闭了
      printf("connect error\n");
      return false;
    }
    buffer.assign(buf, recvSize);
    return true;
  }

  void Close()
  {
    close(_sock);
    _sock = -1;
  }
private:
    int _sock;
};

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值