多路转接IO :select的基本使用

16 篇文章 1 订阅

作者:小 琛
欢迎转载,请标明出处

多路转接IO的意义

多路转接IO,通常也称为多路复用IO。

作用:IO多路转接可以实现对大量描述符的监控,监控的事件可以为可读、可写、异常。
当我们使用多路转接的时候,若某时刻该接口发现了监控下的某个描述符就绪,就会通知进程,进而针对该描述符进行操作,而其它未就绪的描述符则继续监控。
优点:避免了当其它文件描述符未就绪时,进程陷入阻塞的情况

select

接口函数

接口函数:
int select (int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);
函数参数:

  1. nfds:取值通常为最大文件的描述符数值+1,目的是为了提高select的监控效率

  2. fd_set:它的本质是一个结构体,但该结构体内只有一个元素即一个数组fds_bits,该数组被当作位图使用,大小为16 * 64=1024,也符合文件描述fd在内核的本质
    提供了四个操作该位图的函数:
    void FD_CRL(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)//清空

  3. writefds和exceptfds通常给NULL

  4. timeout:超时时间
    struct timeval{
    long tv.sec:秒级
    long tv_usec:毫秒级
    }
    timeval== NULL :阻塞监控
    timeval== 0 (tv_sec0 tv_usec 0):非阻塞监控
    timecal > 0 :带有超时时间的监控

使用技巧
  1. 可以直接将select的所有操作封装为一个类,类成员函数为一个fd_set* 的位图和当前最大的文件描述符
  2. 当需要监控的时候将对应文件描述符加入,调用监控接口,当返回值大于0即有描述符就绪时,将对应描述符通过最大描述符提取出来,随之处理。
  3. 特别注意的点:select接口会将没有就绪的文件描述符从集合中去除掉,因此在使用的时候通常引入一个中间变量,将改变了传入
select的一个简单例子

实现的是一个tcp通信,在服务端采用select进行监控,解决了多个连接时未响应连接导致阻塞的问题。

select.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_;
};

server.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个
  3. select在返回文件描述符的时候,会将未就绪的文件描述符移除掉,导致二次监控的时候,需要再次添加
  4. select无法直接告诉使用者哪个文件描述符就绪了,程序员必须从返回的事件集合中自行判断
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值