Linux C/C++之IO多路复用(select)

简介: 这篇文章主要介绍了TCP的三次握手和四次挥手过程,TCP与UDP的区别,以及如何使用select函数实现IO多路复用,包括服务器监听多个客户端连接和简单聊天室场景的应用示例。

1. TCP的连接与断开

1.1 创建连接过程(三次握手)

  1. 客户端向服务器发送连接请求SYN
  2. 服务器接收到连接请求SYN后, 向客户端发送收到指令ACK和连接请求SYN
  3. 客户端收到服务器发送的ACK和SYN后向服务器发送收到指令ACK

1.2 断开连接过程(四次挥手)

  1. 客户端向服务器发送断开请求FIN
  2. 服务器接收到客户端发送的断开请求FIN后向客户端发送收到指令ACK
  3. 服务器检查是否还有没有收发完的数据, 如果数据已经收发完毕, 服务器向客户端发送断开请求FIN
  4. 客户端接收到服务器发来的断开请求后, 检查是否还有没有接收完的数据,如果没有就向服务器发送收到指令ACK

2. TCP与UDP的区别

  1. TCP有连接, UDP没有连接
  2. TCP是数据流, UDP是数据报文
  3. TCP收发数据相对慢, UDP收发数据相对快(局域网内传输数据用UDP相对较好,它可以极大限度地利用带宽)
  4. TCP安全,稳定,可靠;UDP不安全,不稳定,不可靠(安全: 数据相对不容易被窃取 稳定: 几乎没有传输速率的变化 可靠: 一定能收到数据)
  5. TCP有序(先发送的数据先到, 后发送的数据后到), 数据有边界;UDP无序(可能后发送的数据会先到),数据无边界

3. IO多路复用之select

3.1 select函数

//select函数原型
//监视放在里面的描述符号,有反应返回1, 没有反应返回-1
int select(int nfds,                  //描述符号数量,最大描述符号数加一
           fd_set *readfds,           //描述符号集合(读取)   
           fd_set *writefds,          //描述符号集合(写入)
           fd_set *exceptfds,         //描述符号集合(异常)
           struct timeval *timeout);  //延时


void FD_CLR(int fd,fd_set *set);   //将fd从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);         //将set置为0(清空)

3.2 select函数实现监视标准输入 0

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.h>
#include <fcntl.h>

int main(){

    fd_set fds; //描述符号集合

    FD_ZERO(&fds);  //置零
    FD_SET(0,&fds); //将标准输入设备 0 添加到描述符号集合

    int r;
    char buff[1024] = {0};
    while(1){
        //使用一次阻塞替代多次阻塞
        r = select(1,&fds,NULL,NULL,NULL);
        if(r > 0){
            printf("%d有动静!\n",r);
            scanf("%s",buff);
            printf("接收到了:%s\n",buff);
        }
    }

    return 0;
}

3.3 select函数实现服务器连接多个客户端

服务器(server)端

//服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/select.h>
#include <fcntl.h>

//最多允许的客户端数量
#define NUM 100

int serverSocket,clientSocket[NUM];
int currentNum = 0;      //当前客户端数量

void hand(int val){
    //7. 关闭连接
    for(int i = 0;i < NUM; i++){
        if(-1 != clientSocket[i])
            close(clientSocket[i]);
    }
    close(serverSocket);
    printf("bye bye!\n");
    exit(0);
}
int main(int argc,char* argv[]){
    if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0);
    printf("ip: %s     port:%d\n",argv[1],atoi(argv[2]));

    signal(SIGINT,hand);

    //1. 创建socket 参数一: 协议类型(版本) 参数二: 通信媒介 参数三: 保护方式
    serverSocket = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1);
    printf("创建socket成功!\n");

    //2. 创建服务器协议地址簇
    struct sockaddr_in sAddr = { 0 };
    sAddr.sin_family = AF_INET;        //协议类型 和socket函数第一个参数一致
    sAddr.sin_addr.s_addr = inet_addr(argv[1]);  //将字符串转整数
    sAddr.sin_port = htons(atoi(argv[2]));    //将字符串转整数,再将小端转换成大端

    //3. 绑定服务器协议地址簇
    int r = bind(serverSocket,(struct sockaddr*)&sAddr,sizeof sAddr);
    if(-1 == r) printf("绑定失败:%m\n"),close(serverSocket),exit(-2);
    printf("绑定成功!\n");

    //4. 监听  
    r = listen(serverSocket,10);   //数量
    if(-1 == r) printf("监听失败:%m\n"),close(serverSocket),exit(-3);
    printf("监听成功!\n");


    //初始化客户端描述符号数组
    for (int i = 0; i < NUM; ++i){
        clientSocket[i] = -1;
    }

    //开始监视
    //不仅需要监视serverSocket还要监视每一个返回回来的clientSocket
    fd_set fds;

    int maxFd;       //最大描述符号
    struct sockaddr_in cAddr = {0};
    int len = sizeof(cAddr);
    int cfd;

    char buff[1024] = {0};

    maxFd = 0;
    maxFd = ((maxFd > serverSocket) ? maxFd : serverSocket);

    while(1){

        FD_ZERO(&fds);   //清空

        FD_SET(serverSocket,&fds);    //将服务器socketFd放到监视集合之中

        //将客户端socketFd放到监视集合之中
        for (int i = 0; i < NUM; ++i){
            if(-1 != clientSocket[i]){
                FD_SET(clientSocket[i],&fds);
            }
        }

        //开始监视
        r = select(maxFd+1,&fds,NULL,NULL,NULL);
        if(-1 == r)
            printf("服务器崩溃:%m\n"),close(serverSocket),exit(-1);
        else if(0 == r){
            printf("服务器处于等待状态!\n");
            continue;
        }else{
            //检查是不是serverSocket的动静
            if(FD_ISSET(serverSocket,&fds)){
                cfd = accept(serverSocket,NULL,NULL);
                if(-1 == cfd){
                    printf("客户端连接失败!\n");
                }else{
                    printf("有客户端连接上服务器了:%d\n",cfd);

                    //保存客户端描述符号
                    for (int i = 0; i < NUM; ++i){
                        if(-1 == clientSocket[i]){
                            clientSocket[i] = cfd;
                            maxFd = ((maxFd > cfd) ? maxFd : cfd);
                            break;
                        }
                    }
                }
            }
        }

        //检查客户端是否有动静
        for (int i = 0; i < NUM; ++i){
            if(-1 != clientSocket[i] && FD_ISSET(clientSocket[i],&fds)){
                r = recv(clientSocket[i],buff,1023,0);
                if(r > 0){
                    buff[r] = 0;
                    printf("%d >> %s\n",clientSocket[i], buff);
                }else{
                    printf("客户端: %d 已经断开连接了\n",clientSocket[i]);
                    clientSocket[i] = -1;
                }
            }
        }
    }

    return 0;
}

客户(Client)端

//客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>

int clientSocket;
void hand(int val){
    //5. 关闭连接
    close(clientSocket);
    printf("bye bye!\n");
    exit(0);
}
int main(int argc,char* argv[]){
    if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0);
    printf("ip: %s     port:%d\n",argv[1],atoi(argv[2]));

    signal(SIGINT,hand);

    //1. 创建socket 参数一: 协议类型(版本) 参数二: 通信媒介 参数三: 保护方式
    clientSocket = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == clientSocket) printf("创建socket失败:%m\n"),exit(-1);
    printf("创建socket成功!\n");

    //2. 创建服务器协议地址簇
    struct sockaddr_in cAddr = { 0 };
    cAddr.sin_family = AF_INET;
    cAddr.sin_addr.s_addr = inet_addr(argv[1]);  //将字符串转整数
    cAddr.sin_port = htons(atoi(argv[2]));    //将字符串转整数,再将小端转换成大端

    //3.连接服务器
    int r = connect(clientSocket,(struct sockaddr*)&cAddr,sizeof cAddr);
    if(-1 == r) printf("连接服务器失败:%m\n"),close(clientSocket),exit(-2);
    printf("连接服务器成功!\n");


    //4. 通信
    char buff[256] = {0};
    while(1){
        printf("你想要发送:");
        scanf("%s",buff);
        send(clientSocket,buff,strlen(buff),0);
    }

    return 0;
}

3.4 select函数实现简单聊天室

服务器(Server)端

//服务器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <sys/select.h>
#include <fcntl.h>

//最多允许的客户端数量
#define NUM 100

int serverSocket,clientSocket[NUM];
int currentNum = 0;      //当前客户端数量

void hand(int val){
    //7. 关闭连接
    for(int i = 0;i < NUM; i++){
        if(-1 != clientSocket[i])
            close(clientSocket[i]);
    }
    close(serverSocket);
    printf("bye bye!\n");
    exit(0);
}

int main(int argc,char* argv[]){
    if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0);
    printf("ip: %s     port:%d\n",argv[1],atoi(argv[2]));

    signal(SIGINT,hand);

    //1. 创建socket 参数一: 协议类型(版本) 参数二: 通信媒介 参数三: 保护方式
    serverSocket = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == serverSocket) printf("创建socket失败:%m\n"),exit(-1);
    printf("创建socket成功!\n");

    //2. 创建服务器协议地址簇
    struct sockaddr_in sAddr = { 0 };
    sAddr.sin_family = AF_INET;        //协议类型 和socket函数第一个参数一致
    sAddr.sin_addr.s_addr = inet_addr(argv[1]);  //将字符串转整数
    sAddr.sin_port = htons(atoi(argv[2]));    //将字符串转整数,再将小端转换成大端

    //3. 绑定服务器协议地址簇
    int r = bind(serverSocket,(struct sockaddr*)&sAddr,sizeof sAddr);
    if(-1 == r) printf("绑定失败:%m\n"),close(serverSocket),exit(-2);
    printf("绑定成功!\n");

    //4. 监听  
    r = listen(serverSocket,10);   //数量
    if(-1 == r) printf("监听失败:%m\n"),close(serverSocket),exit(-3);
    printf("监听成功!\n");


    //初始化客户端描述符号数组
    for (int i = 0; i < NUM; ++i){
        clientSocket[i] = -1;
    }
    //开始监视
    //不仅需要监视serverSocket还要监视每一个返回回来的clientSocket
    fd_set fds;

    int maxFd;       //最大描述符号
    struct sockaddr_in cAddr = {0};
    int len = sizeof(cAddr);
    int cfd;

    char buff[1024] = {0};

    maxFd = 0;
    maxFd = ((maxFd > serverSocket) ? maxFd : serverSocket);

    while(1){
        FD_ZERO(&fds);   //清空监视集合

        FD_SET(serverSocket,&fds);    //将服务器socketFd放到监视集合之中

        //将客户端socketFd放到监视集合之中
        for (int i = 0; i < NUM; ++i){
            if(-1 != clientSocket[i]){
                FD_SET(clientSocket[i],&fds);
            }
        }

        //开始监视
        r = select(maxFd+1,&fds,NULL,NULL,NULL);
        if(-1 == r)
            printf("服务器崩溃:%m\n"),close(serverSocket),exit(-1);
        else if(0 == r){
            printf("服务器处于等待状态!\n");
            continue;
        }else{
            //检查是不是serverSocket的动静
            if(FD_ISSET(serverSocket,&fds)){
                cfd = accept(serverSocket,NULL,NULL);
                if(-1 == cfd){
                    printf("客户端连接失败!\n");
                }else{
                    printf("有客户端连接上服务器了:%d\n",cfd);

                    //保存客户端描述符号
                    for (int i = 0; i < NUM; ++i){
                        if(-1 == clientSocket[i]){
                            clientSocket[i] = cfd;
                            maxFd = ((maxFd > cfd) ? maxFd : cfd);
                            break;
                        }
                    }
                }
            }
        }
        //检查客户端是否有动静
        for (int i = 0; i < NUM; ++i){
            if(-1 != clientSocket[i] && FD_ISSET(clientSocket[i],&fds)){
                r = recv(clientSocket[i],buff,1023,0);
                if(r > 0){
                    buff[r] = 0;
                    printf("%d >> %s\n",clientSocket[i], buff);

                    //服务器将数据转发给每一个在线的客户端(除了发消息给服务器的客户端)
                    char tBuff[2048];
                    sprintf(tBuff,"来自%d客户端发给服务器的消息:%s",clientSocket[i],buff);
                    for(int j = 0; j < NUM; j++){
                        if(-1 != clientSocket[j] && clientSocket[i] != clientSocket[j]){
                            send(clientSocket[j],tBuff,strlen(tBuff),0);
                        }
                    }
                }else{
                    printf("客户端: %d 已经断开连接了\n",clientSocket[i]);
                    clientSocket[i] = -1;
                }
            }
        }
    }

    return 0;
}

客户(Client)端

//客户端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>

int clientSocket;
void hand(int val){
    //5. 关闭连接
    close(clientSocket);
    printf("bye bye!\n");
    exit(0);
}
int main(int argc,char* argv[]){
    if(argc != 3) printf("请输入ip地址和端口号!\n"),exit(0);
    printf("ip: %s     port:%d\n",argv[1],atoi(argv[2]));

    signal(SIGINT,hand);

    //1. 创建socket 参数一: 协议类型(版本) 参数二: 通信媒介 参数三: 保护方式
    clientSocket = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == clientSocket) printf("创建socket失败:%m\n"),exit(-1);
    printf("创建socket成功!\n");

    //2. 创建服务器协议地址簇
    struct sockaddr_in cAddr = { 0 };
    cAddr.sin_family = AF_INET;
    cAddr.sin_addr.s_addr = inet_addr(argv[1]);  //将字符串转整数
    cAddr.sin_port = htons(atoi(argv[2]));    //将字符串转整数,再将小端转换成大端

    //3.连接服务器
    int r = connect(clientSocket,(struct sockaddr*)&cAddr,sizeof cAddr);
    if(-1 == r) printf("连接服务器失败:%m\n"),close(clientSocket),exit(-2);
    printf("连接服务器成功!\n");


    //开始监视
    //不仅要监视标准输入设备, 还要监视clientSocket服务器是否发送数据
    fd_set fds;

    int maxFd = clientSocket > 0 ? clientSocket : 0;
    char buff[2048] = {0};
    while(1){
        //清空集合
        FD_ZERO(&fds);
        //将标准输入输出放入到集合中
        FD_SET(0,&fds);
        //将clientSocket放入到监视集合中
        FD_SET(clientSocket,&fds);

        //开始监视
        r = select(maxFd + 1, &fds, NULL,NULL,NULL);
        if(-1 == r)
            printf("客户端崩溃:%m\n"),close(clientSocket),exit(-1);
        else if(0 == r){
            printf("客户端处于等待状态!\n");
            continue;
        }else{
            memset(buff,0,2048);
            //如果 0 有动静就向服务器发消息
            if(FD_ISSET(0,&fds)){
                scanf("%s",buff);
                send(clientSocket,buff,strlen(buff),0);
                continue;
            }
            //如果 clientSocket有动静就接收服务器发来的消息
            if(FD_ISSET(clientSocket,&fds) && -1 != clientSocket){
                memset(buff,0,2048);
                printf("服务器发来了客户端的消息!\n");
                r = recv(clientSocket,buff,2047,0);
                if(r > 0){
                    buff[r] = 0;
                    printf("服务器发来消息 >> %s\n",buff);
                }
            }
        }
    }

    return 0;
}

相关文章
|
27天前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
3天前
|
人工智能 Rust Java
10月更文挑战赛火热启动,坚持热爱坚持创作!
开发者社区10月更文挑战,寻找热爱技术内容创作的你,欢迎来创作!
370 16
|
19天前
|
存储 关系型数据库 分布式数据库
GraphRAG:基于PolarDB+通义千问+LangChain的知识图谱+大模型最佳实践
本文介绍了如何使用PolarDB、通义千问和LangChain搭建GraphRAG系统,结合知识图谱和向量检索提升问答质量。通过实例展示了单独使用向量检索和图检索的局限性,并通过图+向量联合搜索增强了问答准确性。PolarDB支持AGE图引擎和pgvector插件,实现图数据和向量数据的统一存储与检索,提升了RAG系统的性能和效果。
|
6天前
|
JSON 自然语言处理 数据管理
阿里云百炼产品月刊【2024年9月】
阿里云百炼产品月刊【2024年9月】,涵盖本月产品和功能发布、活动,应用实践等内容,帮助您快速了解阿里云百炼产品的最新动态。
阿里云百炼产品月刊【2024年9月】
|
21天前
|
人工智能 IDE 程序员
期盼已久!通义灵码 AI 程序员开启邀测,全流程开发仅用几分钟
在云栖大会上,阿里云云原生应用平台负责人丁宇宣布,「通义灵码」完成全面升级,并正式发布 AI 程序员。
|
23天前
|
机器学习/深度学习 算法 大数据
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
2024“华为杯”数学建模竞赛,对ABCDEF每个题进行详细的分析,涵盖风电场功率优化、WLAN网络吞吐量、磁性元件损耗建模、地理环境问题、高速公路应急车道启用和X射线脉冲星建模等多领域问题,解析了问题类型、专业和技能的需要。
2592 22
【BetterBench博士】2024 “华为杯”第二十一届中国研究生数学建模竞赛 选题分析
|
5天前
|
存储 人工智能 搜索推荐
数据治理,是时候打破刻板印象了
瓴羊智能数据建设与治理产品Datapin全面升级,可演进扩展的数据架构体系为企业数据治理预留发展空间,推出敏捷版用以解决企业数据量不大但需构建数据的场景问题,基于大模型打造的DataAgent更是为企业用好数据资产提供了便利。
181 2
|
3天前
|
编译器 C#
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
C#多态概述:通过继承实现的不同对象调用相同的方法,表现出不同的行为
105 65
|
7天前
|
Linux 虚拟化 开发者
一键将CentOs的yum源更换为国内阿里yum源
一键将CentOs的yum源更换为国内阿里yum源
332 2
|
23天前
|
机器学习/深度学习 算法 数据可视化
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码
2024年中国研究生数学建模竞赛C题聚焦磁性元件磁芯损耗建模。题目背景介绍了电能变换技术的发展与应用,强调磁性元件在功率变换器中的重要性。磁芯损耗受多种因素影响,现有模型难以精确预测。题目要求通过数据分析建立高精度磁芯损耗模型。具体任务包括励磁波形分类、修正斯坦麦茨方程、分析影响因素、构建预测模型及优化设计条件。涉及数据预处理、特征提取、机器学习及优化算法等技术。适合电气、材料、计算机等多个专业学生参与。
1580 17
【BetterBench博士】2024年中国研究生数学建模竞赛 C题:数据驱动下磁性元件的磁芯损耗建模 问题分析、数学模型、python 代码