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

本文涉及的产品
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
容器服务 Serverless 版 ACK Serverless,952元额度 多规格
简介: 这篇文章主要介绍了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;
}

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
1月前
|
存储 Linux C语言
Linux C/C++之IO多路复用(aio)
这篇文章介绍了Linux中IO多路复用技术epoll和异步IO技术aio的区别、执行过程、编程模型以及具体的编程实现方式。
82 1
Linux C/C++之IO多路复用(aio)
|
1月前
|
Ubuntu Linux 编译器
Linux/Ubuntu下使用VS Code配置C/C++项目环境调用OpenCV
通过以上步骤,您已经成功在Ubuntu系统下的VS Code中配置了C/C++项目环境,并能够调用OpenCV库进行开发。请确保每一步都按照您的系统实际情况进行适当调整。
274 3
|
1月前
|
资源调度 Linux 调度
Linux C/C++之线程基础
这篇文章详细介绍了Linux下C/C++线程的基本概念、创建和管理线程的方法,以及线程同步的各种机制,并通过实例代码展示了线程同步技术的应用。
29 0
Linux C/C++之线程基础
|
1月前
|
Linux C++
Linux C/C++之IO多路复用(poll,epoll)
这篇文章详细介绍了Linux下C/C++编程中IO多路复用的两种机制:poll和epoll,包括它们的比较、编程模型、函数原型以及如何使用这些机制实现服务器端和客户端之间的多个连接。
24 0
Linux C/C++之IO多路复用(poll,epoll)
|
3月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
4月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
2月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
|
3月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
46 2
|
3月前
|
XML 存储 JSON
【IO面试题 六】、 除了Java自带的序列化之外,你还了解哪些序列化工具?
除了Java自带的序列化,常见的序列化工具还包括JSON(如jackson、gson、fastjson)、Protobuf、Thrift和Avro,各具特点,适用于不同的应用场景和性能需求。
|
3月前
|
缓存 Java
【IO面试题 一】、介绍一下Java中的IO流
Java中的IO流是对数据输入输出操作的抽象,分为输入流和输出流,字节流和字符流,节点流和处理流,提供了多种类支持不同数据源和操作,如文件流、数组流、管道流、字符串流、缓冲流、转换流、对象流、打印流、推回输入流和数据流等。
【IO面试题 一】、介绍一下Java中的IO流