多路IO复用

目录

1,TCP通信原理

如上图所示,

左侧有两大块内存,一个是负责监听客户端连接的部分,另一个是负责和客户端通信的部分。

右侧的内存是客户端的部分,用于连接服务端,以及和服务端进行通信。

服务端需要用两个文件描述符去操作这两块内存。一个是用于监听的文件描述符,一个是用于通信的文件描述符。

在服务端调用 accept() 函数其实就是监听客户端有没有对服务端进行连接。如果没有连接就会一直阻塞。

2,select 函数

select函数的作用在于去检测一个 线性表中所存储的所有文件描述符的状态。用于处理服务端与多个客户端通信时使用。

复制代码
#include<sys/select.h>
struct  timeval{
   time_t    tv_sec;        //seconds 秒
   suseconds_t  tv_usec;    //microseconds 微秒 
};

int  select(int nfds , fd_set* readfds , fd_set* writefds , fd_set* exceptfds , 
               struct timeval* timeout );
 
//函数返回值
           检测的符合条件的文件描述符的个数。               
//函数参数:
nfds: 委托内核检测的这三个集合中最大的文件描述符 + 1。
         内核需要线性便利这些集合中的文件描述符,这个值是循环结束的条件
         在windows中这个参数是无效的,指定为  -1  即可。
readfds:  文件描述符的读集合,
               内核只检测这个集合中文件描述符对应的读缓冲区
               传入传出参数,读集合一般情况下都是需要检测的,这样才知道通过哪个文件描述符接收数据
writefds: 文件描述符的写集合,
              内核只检测这个集合中文件描述符对应的写缓冲区
              传入传出参数,如果不需要这个参数可以指定为NULL。
exceptfds: 异常集合,用于查看用于检测的这些读写文件描述符的集合有无异常。
timeout: 检测时长,超过检测时长,就会返回。操作时长就是秒加上微妙。

第一章我们了解了TCP服务端通信需要两种文件描述符,实际在通信的过程当中一个服务端可能需要与多个客户端通信,所以需要 N 个用于通信的文件描述符和 1 个用于监听的文件描述符。

所以 select 函数需要检测 N + 1 个文件描述符。

但实际上这个被用于检测的线性表能装的文件描述符的最大数量是 1024 个

如下图所示为一个文件描述符表,此表为内核中的表。

我们通过select函数,将一系列的文件描述符集合传递拷贝到内核中,然后内核会生成一个线性表用于存储,然后我们去循环遍历这个线性表,当检测到有文件描述符状态发生变化,就会传出对应的三个集合。

2.1,select 函数 fd_set集合

占内存 1024 个标志位。一个标志位对应一个文件描述符。占用128个字节。

系统提供了一些操作函数,让我们把文件描述符放入 fd_set 集合中,除此之外还支持其他操作。

如下:

复制代码
//1,将文件描述符 fd 从set集合中删除  ==  将fd对应的标志位设置为0
void  FD_CLR(int  fd,fd_set* set);

//2,判断文件描述符 fd 是否在 set 集合中 == 读一下fd对应的标志位到底是 0 还是 1
int  FD_ISSET(int fd,fd_set* set);

//3,将文件描述符fd添加到 set 集合中 == 将fd对应的标志位设置为1
void  FD_SET(int fd,fd_set* set);

//4,将set集合中,所有文件描述符对应的标志位设置为0,集合中没有添加任何文件描述符
void  FD_ZERO(fd_set* set);

2.2,使用select来处理并发


select函数实现TCP并发处理

javascript 复制代码
#include <iostream>
#include <vector>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <cstring>

#define PORT 8080
#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int main() 
{

    //1,获取监听文件描述符
    int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket < 0) {
        std::cerr << "Socket creation failed" << std::endl;
        return -1;
    }

    //2,绑定IP和端口
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(PORT);
    if (bind(serverSocket, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) < 0) {
        std::cerr << "Bind failed" << std::endl;
        close(serverSocket);
        return -1;
    }
    if (listen(serverSocket, MAX_CLIENTS) < 0) {
        std::cerr << "Listen failed" << std::endl;
        close(serverSocket);
        return -1;
    }

    //3,将监听的文件描述符添加到 fd_set 集合中
    std::vector<int> clientSockets(MAX_CLIENTS, -1);
    fd_set readFds;
    fd_set tmpFds;
    FD_ZERO(&readFds);
    FD_SET(serverSocket, &readFds);

    //4,用select进行检测
    while (true) {
        tmpFds = readFds;
        int activity = select(serverSocket + 1, &tmpFds, nullptr, nullptr, nullptr);
        if (activity < 0) 
        {
            std::cerr << "Select error" << std::endl;
            break;
        } 
        else if (activity > 0) 
        {   
            //4.1 通过 accept() 获取客户端文件描述符,并将客户端文件描述符添加到 clientSockets 数组中。
            if (FD_ISSET(serverSocket, &tmpFds)) 
            {   
                sockaddr_in clientAddr;
                socklen_t clientAddrLen = sizeof(clientAddr);
                int newSocket = accept(serverSocket, reinterpret_cast<sockaddr*>(&clientAddr), &clientAddrLen);
                if (newSocket < 0) 
                {
                    std::cerr << "Accept failed" << std::endl;
                    continue;
                }
                
                for (size_t i = 0; i < clientSockets.size(); ++i) 
                {
                    if (clientSockets[i] == -1) 
                    {
                        clientSockets[i] = newSocket;
                        FD_SET(newSocket, &readFds);
                        break;
                    }
                }
            }
            
            //4.2  遍历 clientSockets 数组,查看所有通信的文件描述符的状态,并进行通信处理。
            for (size_t i = 0; i < clientSockets.size(); ++i) 
            {
                if (clientSockets[i] != -1 && FD_ISSET(clientSockets[i], &tmpFds)) 
                {
                    char buffer[BUFFER_SIZE];
                    int valread = read(clientSockets[i], buffer, BUFFER_SIZE);
                    if (valread <= 0) 
                    {
                        close(clientSockets[i]);
                        FD_CLR(clientSockets[i], &readFds);
                        clientSockets[i] = -1;
                    } else 
                    {
                        buffer[valread] = '\0';
                        std::cout << "Received from client " << i << ": " << buffer << std::endl;
                    }
                }
            }
        }
    }

    close(serverSocket);
    for (int sock : clientSockets) 
    {
        if (sock != -1) 
        {
            close(sock);
        }
    }

    return 0;
}
相关推荐
培培说证3 小时前
2026大专Java开发工程师,考什么证加分?
java·开发语言·python
qq_336313933 小时前
java基础-方法引用
java·开发语言·算法
总是学不会.3 小时前
【JUC编程】一、线程的基础概念
java·开发语言·jvm
我是唐青枫3 小时前
C#.NET struct 全解析:什么时候该用值类型?
开发语言·c#·.net
沉下去,苦磨练!3 小时前
计算一个字符串在另一个字符串中出现次数
java·开发语言
froginwe113 小时前
Bootstrap5 表格
开发语言
前端不太难3 小时前
Navigation State 驱动的页面调试方法论
开发语言·前端·react.js
饕餮怪程序猿3 小时前
订单分批算法设计与实现:基于商品相似性的智能分拣优化(C++)
开发语言·c++·算法
崇山峻岭之间4 小时前
Matlab学习记录05
开发语言·学习·matlab