计算机网络之多路转接select

个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创

计算机网络之多路转接select

收录于专栏【计算机网络
本专栏旨在分享学习计算机网络的一点学习笔记,欢迎大家在评论区交流讨论💌

目录

[初始 select](#初始 select)

[select 函数原型](#select 函数原型)

[理解 select 执行过程](#理解 select 执行过程)

[socket 就绪条件](#socket 就绪条件)

[select 的特点](#select 的特点)

[select 缺点](#select 缺点)

[select 使用示例:检测标准输入输出](#select 使用示例:检测标准输入输出)


初始 select

系统提供 select 函数来实现多路复用输入/输出模型。

select 系统调用是让我们的程序监视多个文件描述符的状态变化的

程序会停在 select 这里等待,知道被监视的文件描述符有一个或多个发生了状态变化;

select 函数原型

select 的函数原型如下:

cpp 复制代码
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set
*exceptfds, struct timeval *timeout);

参数解释:

参数 nfds 是需要监视的最大的文件描述符 +1;

rdset,wrset,exset 分别对应于需要检测的可读文件描述符的集合,可写文件描述符的集合及异常文件描述符的集合;

参数 timeout 为结构 timeval,用来设置 select()的等待时间;

参数 timeout 取值:

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

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

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

关于 fd_set 的结构

其实这个结构就是一个整数数组,更严格的说,是一个 "位图"。使用位图中对应的位来表示要监视的文件描述符。

提供了一组操作 fd_set 的接口,来比较方便的操作位图。

cpp 复制代码
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 的全部位

关于 timeval 结构

timeval 结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0.

cpp 复制代码
struct timeval
{
    __time_t tv_sec;
    __suseconds_t tv_usec;
};

函数返回值:

执行成功则返回文件描述词状态改变的个数

如果返回0代表在描述词状态改变前已超过 timeout 时间,没有返回

当有错误发生时则返回-1,错误原因存于 errno,此时参数 readfds,writefds,exceptfds 和 timeout 的值变成不可预测。

错误值可能为:

EBADF 文件描述词为无效的或文件已关闭

EINTR 此调用被信号所中断

EINVAL 参数 n 为负值

ENOMEM 核心内存不足

常见的程序片段如下:

cpp 复制代码
fs_set readset;
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset)){......}

理解 select 执行过程

理解 select 模型的关键在于理解 fd_set,为说明方便,取 fd_set 长度为 1 字节,fd_set 中每一 bit可以对应一个文件描述符 fd。则 1 字节长的 fd_set 最大可以对应 8 个 fd。

(1)执行 fd_set set;FD_ZERO(&set);则 set 用位表示是 0000,0000。

(2)若 fd = 5,执行 FD_SET(fd,&set);后 set 变为 0001,0000(第 5 位置为 1)

(3)若再加入 fd = 2,fd = 1,则 set 变为 0001,0011

(4)执行 select(6, &set, 0, 0, 0) 阻塞等待

(5)若 fd = 1,fd = 2 上都发生可读事件,则 select 返回,此时 set 变为 0000,0011。注意:没有事件发生的 fd = 5 被清空。

socket 就绪条件

读就绪

socket 内核中,接收缓冲区中的字节数,大于等于低水位标记 SO_RCVLOWAT。此时可以无阻塞的读该文件描述符,并且返回值大于0;

socket TCP 通信中,对端关闭连接,此时对该 socket 读,则返回 0;

监听的 socket 上有新的连接请求;

socket 上有未处理的错误;

写就绪

socket 内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记 SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于0;

socket 的写操作被关闭(close 或者 shutdown)。对一个写操作被关闭的 socket 进行写操作,会触发 SIGPIPE 信号;

socket 使用非阻塞 connect 连接成功或失败之后;

socket 上有未读取的错误;

异常就绪

socket 上收到带外数据。关于带外数据,和 TCP 紧急模式相关(回忆 TCP 协议头中,有一个紧急指针的字段)

select 的特点

可监控的文件描述符个数取决于 sizeof(fd_set) 的值。我这边服务器上 sizeof(fd_set) = 512,每 bit 表示一个文件描述符,则我服务器上支持的最大文件描述符是 512*8 = 4096 。

将 fd 加入 select 监控集的同时,还要再使用一个数据结构 array 保存放到 select 监控集中的 fd:

一使用于再 select 返回后,array 作为源数据和 fd_set 进行 FD_ISSET 判断。

二是 select 返回后会把以前加入的但并无事件发生的 fd 清空,则每次开始 select 前都要重新从 array 取得 fd 逐一加入(FD_ZERO最先),扫描 array 的同时取得 fd 最大值 maxfd,用于 select 的第一个参数。

fd_set 的大小可以调整,可能涉及到重新编译内核。

select 缺点

每次调用 select,都需要手动设置 fd 集合,从接口使用角度来说也非常不便。

每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时候会很大

同时每次调用 select 都需要在内核遍历传递进来的所有 fd,这个开销 在 fd 很多时候会很大

select 支持的文件描述符数量太小。

select 使用示例:检测标准输入输出

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
int main()
{
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(0, &read_fds);
    for (;;)
    {
        printf("> ");
        fflush(stdout);
        int ret = select(1, &read_fds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select");
            continue;
        }
        if (FD_ISSET(0, &read_fds))
        {
            char buf[1024] = {0};
            read(0, buf, sizeof(buf) - 1);
            printf("input: %s", buf);
        }
        else
        {
            printf("error! invaild fd\n");
            continue;
        }
        FD_ZERO(&read_fds);
        FD_SET(0, &read_fds);
    }
    return 0;
}

当只有文件描述符 0 (标准输入)时,因为输入条件只有在你输入信息的时候,才成立,所有如果一直不输入,就会产生超时信息。

int ret = select(1, &read_fds, NULL, NULL, NULL);

select 函数会阻塞直到某些文件描述符准备好进行读写操作。在这里:

1 表示最多监视一个文件描述符。

&read_fds 是一个指向 fd_set 结构的指针,表示监视哪些文件描述符的读事件。

NULL 表示不关心写事件。

NULL 表示不关心异常事件。

最后一个 NULL 参数表示 select 函数将永远阻塞,直到文件描述符就绪。

select 返回值表示就绪的文件描述符数量,若返回值小于 0,则说明出现了错误。

**FD_ISSET(0, &read_fds)**用来检查文件描述符 0(标准输入)是否有数据可读。如果标准输入有数据,则执行读取操作:

**read(0, buf, sizeof(buf) - 1)**从标准输入读取数据存储到 buf 中,最多读取 1023 个字节(因为最后留了一个位置给 \0)。

然后打印出用户输入的内容。

相关推荐
秋风&萧瑟14 分钟前
【数据结构】顺序队列与链式队列
linux·数据结构·windows
Cici_ovo17 分钟前
wlan和vlan
网络·智能路由器
约定Da于配置4 小时前
uniapp封装websocket
前端·javascript·vue.js·websocket·网络协议·学习·uni-app
hardWork_yulu5 小时前
Android RTMP直播练习实践
网络·安卓
qq_243050796 小时前
irpas:互联网路由协议攻击套件!全参数详细教程!Kali Linux入门教程!黑客渗透测试!
linux·网络·web安全·网络安全·黑客·渗透测试·系统安全
肖田变强不变秃7 小时前
C++实现矩阵Matrix类 实现基本运算
开发语言·c++·matlab·矩阵·有限元·ansys
大丈夫立于天地间8 小时前
机遇、挑战与融合创新之路
网络
青旋.8 小时前
数据链路层——以太网协议
网络·网络协议·tcp/ip
月光技术杂谈9 小时前
OSI&5G&WIFI&自组网协议层次对比
tcp/ip·5g·wifi·osi·自组网·七层模式·协议架构
东锋1.310 小时前
计算机网络中常用的端口号以及对应的应用程序
网络