【计算机网络】IO复用方法(二)——Select

目录

[一、select 方法概述](#一、select 方法概述)

[二、select 系统调用原型](#二、select 系统调用原型)

[三、select 的基本原理](#三、select 的基本原理)

[四、使用 select 的步骤](#四、使用 select 的步骤)

五、将select应用到tcp服务端

[六、select 的优缺点](#六、select 的优缺点)

、select 方法概述

select 是 Unix/Linux 系统中一种经典的 I/O 多路复用技术,允许程序监视多个文件描述符(如套接字、管道等),直到其中一个或多个描述符准备好进行 I/O 操作(如可读、可写或发生异常)。select 适用于需要同时处理多个 I/O 通道的场景,如网络服务器、客户端等。

二、select 系统调用原型

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

参数设置:

1、nfds参数:指定被监听的文件描述符的总数。它通常被设置为select 监听的所有文件描述符中的最大值加1,因为文件描述符是从0开始计数的。

2、readfds、writefds和exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符集合。应用程序调用select函数时,通过这3个参数传入自己感兴趣的文件描述符。select调用返回时,内核将修改它们来通知应用程序哪些文件描述符已经就绪。一般只关注读,后面两个参数设置为空指针。这三个参数是fd_set结构指针类型。

3、timeout:代表超时时间;设为 NULL 表示无限阻塞,设为 0 表示非阻塞轮询。

三、select 的基本原理

select 通过三个文件描述符集合(fd_set)来监视不同类型的 I/O 事件:

  • readfds:监视描述符是否可读(如数据到达、连接关闭)。
  • writefds:监视描述符是否可写(如发送缓冲区空闲)。
  • exceptfds:监视描述符是否发生异常(如带外数据到达)。

调用 select 时,内核会阻塞进程,直到至少一个描述符就绪或超时。返回后,内核会修改这些集合,仅保留就绪的描述符。

给select传入一个集合,会返回告诉这个集合中有多少个就绪,集合中就绪对应的位会被设置成1,没有就绪就会设置成0。最多可以检测1024给描述符。同时select为周期性的调用。

四、使用 select 的步骤

cpp 复制代码
int main(){
    int fd=STDIN;//输入
    fd_set fdset;
    while (1)//把描述符添加到集合中
    {
        FD_ZERO(&fdset);//清空集合
        FD_SET(fd,&fdset);//将描述符添加到集合里
        struct timeval tv={5,0};//设置超时时间,时间每次重置
        int n=select(fd+1,&fdset,NULL,NULL,&tv);//阻塞,最多阻塞5s
        if(n==-1){
            printf("select err\n");
        }else if(n==0){//超时
            printf("time out\n");
        }else{
            if(FD_ISSET(fd,&fdset))//真,fd有读事件发生
            {
                char buff[128]={0};
                read(fd,buff,127);
                printf("read:%s\n",buff);
            }
    }
}

初始化描述符集合: 使用 FD_ZERO 清空集合,FD_SET 添加需要监视的描述符。

调用 select 传入初始化后的集合和超时时间。select 会阻塞直到事件就绪或超时。

检查返回值

  • 返回 -1 表示错误(如被信号中断)。
  • 返回 0 表示超时。
  • 返回正数表示就绪的描述符数量。

处理就绪的描述符 使用 FD_ISSET 检查哪些描述符就绪,并执行相应操作

五、将select应用到tcp服务端

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>

#define MAXFD 10

int socket_init();
void fds_init(int fds[]){//初始化数组
    for(int i = 0; i < MAXFD; i++){
        fds[i] = -1;
    }
}
//添加
void fds_add(int fds[], int fd){
    for(int i = 0; i < MAXFD; i++ ){
        if( fds[i] == -1){
            fds[i] = fd;
            break;//当找到一个-1,就停止
        }
    }
}
//移除
void fds_del(int fds[], int fd){
    for(int i = 0; i < MAXFD; i++){
        if( fds[i] == fd){
            fds[i] = -1;
            break;
        }
    }
}
int main(){
    int sockfd = socket_init();//套接字
    if( sockfd == -1){
        exit(1);
    }
    int fds[MAXFD];//数组
    fds_init(fds);//-1,初始化数组,为空
    fds_add(fds,sockfd);//添加描述符
    fd_set fdset;//select
    while( 1 ){
        FD_ZERO(&fdset);//清空
        int maxfd = -1;
        //将所有描述符添加到集合中
        for(int i = 0; i < MAXFD; i++){
            if( fds[i] == -1){//找到有效的
                continue;
            }
            FD_SET(fds[i],&fdset);//添加
            if( maxfd < fds[i]){//找到最大的描述符
                maxfd = fds[i];
            }
        }
        struct timeval tv = {5,0};//超时时间
        int n = select(maxfd+1,&fdset,NULL,NULL,&tv);//阻塞,5s
        if( n == -1){
            printf("select err\n");
        }else if( n == 0 ){//超时
            printf("time out\n");
        }else{//有n个描述符就绪,遍历整个数组找到有效的
            for(int i = 0; i < MAXFD; i++){
                if( fds[i] == -1){//无效描述符
                    continue;
                }
                if( FD_ISSET(fds[i],&fdset)){//真,有事件发生,检测有无事件发生
                    //判断套接字类型
                    if( fds[i] == sockfd ){//监听套接字 accept
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);
                        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
                        if( c < 0 ){
                            continue;
                        }
                        printf("accept c=%d\n",c);//接受链接
                        fds_add(fds,c);  //把c加入到数组中,
                        /*因为等待该循环将事件监测完毕,进入下一轮循环时,又重新清空集合,
                        将数组的所有元素重新添加至集合,此时c也被加入到集合中,
                        再次进行select检测,下一轮就有两个描述符以供检测*/
                    }
                    else{//连接套接字有数据 recv
                        char buff[128] = {0};
                        int num = recv(fds[i],buff,127,0);
                        if( num <= 0 ){
                            close(fds[i]);
                            fds_del(fds,fds[i]);
                            printf("close\n");
                        }
                        else{
                            printf("recv:%s\n",buff);
                            send(fds[i],"ok",2,0);
                        }
                    }
                }
            }
        }
    }
}
int socket_init(){//创建监听套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if( sockfd == -1){//创建套接字失败
        return -1;
    }
    struct sockaddr_in saddr;//ipv4专用,制定ip端口
    memset(&saddr,0,sizeof(saddr));//清空
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定,通用套接字
    if( res == -1){
        printf("bind err\n");
        return -1;
    }
    res = listen(sockfd,5);//监听队列
    if( res == -1){
        return -1;
    }
    return sockfd;
}

客户端:

服务端:

六、select 的优缺点

优点

  • 跨平台支持,几乎在所有 Unix-like 系统上可用。

  • 实现简单,适合少量连接或对性能要求不高的场景。

缺点

  • 文件描述符数量受限(通常为 1024),高并发场景性能较差。

  • 每次调用需重新传入描述符集合,内核和用户空间频繁拷贝数据。

  • 线性扫描所有描述符,效率随描述符数量增加而下降。

相关推荐
蒙奇D索大9 小时前
【计算机网络】考研408计算机网络:传输介质(导向/非导向)考点梳理
笔记·计算机网络·考研·408·改行学it
甄心爱学习1 天前
计算机网络7
计算机网络
报错小能手1 天前
计算机网络自顶向下方法11——应用层 详解HTTP/3
网络协议·计算机网络·http
lin张1 天前
一文读懂计算机网络参考模型(二)
网络·计算机网络
报错小能手2 天前
计算机网络自顶向下方法15——应用层 P2P文件分发与BitTorrent协议
计算机网络
_殊途2 天前
计算机网络-物理层
计算机网络
报错小能手2 天前
计算机网络自顶向下方法4——详解协议层次及其服务模型
服务器·计算机网络·php
报错小能手3 天前
计算机网络自顶向下方法16——应用层 因特网视频 HTTP流和DASH
开发语言·计算机网络·php
番茄寿司3 天前
基于LSTM的多变量时间序列预测创新路径
论文阅读·深度学习·计算机网络·机器学习·lstm