MST
星途 面试题库

面试题:C++中select模式同步特性之基础应用

在C++网络编程使用select模式时,假设你有一个服务器程序需要同时监听多个客户端连接并处理其请求,简述如何利用select模式的同步特性来实现。请给出关键代码片段并解释每部分的作用。
32.6万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试
  1. 基本原理

    • select 模式是一种同步I/O多路复用技术。它允许程序监视一组文件描述符(在网络编程中通常是套接字),当其中任何一个文件描述符准备好进行读、写或有异常事件发生时,select 函数会返回,程序可以据此对准备好的文件描述符进行相应操作。
  2. 关键代码片段及解释

#include <iostream>
#include <winsock2.h> // Windows下的套接字库,Linux下用<sys/socket.h>等
#include <vector>

#pragma comment(lib, "ws2_32.lib") // Windows下链接ws2_32库

int main() {
    // 初始化Winsock(Windows下),Linux下无需此步骤
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // 创建监听套接字
    SOCKET listenSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSocket == INVALID_SOCKET) {
        std::cerr << "Failed to create socket" << std::endl;
        return 1;
    }

    // 绑定地址和端口
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_addr.s_addr = INADDR_ANY;
    serverAddr.sin_port = htons(12345); // 假设端口为12345
    if (bind(listenSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
        std::cerr << "Failed to bind socket" << std::endl;
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }

    // 开始监听
    if (listen(listenSocket, 5) == SOCKET_ERROR) {
        std::cerr << "Failed to listen" << std::endl;
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }

    fd_set readFds;
    FD_ZERO(&readFds);
    FD_SET(listenSocket, &readFds);
    std::vector<SOCKET> clientSockets;

    while (true) {
        fd_set tempFds = readFds;
        int activity = select(0, &tempFds, nullptr, nullptr, nullptr);
        if (activity == SOCKET_ERROR) {
            std::cerr << "select error" << std::endl;
            break;
        } else if (activity > 0) {
            if (FD_ISSET(listenSocket, &tempFds)) {
                // 有新的客户端连接
                SOCKET clientSocket = accept(listenSocket, nullptr, nullptr);
                if (clientSocket != INVALID_SOCKET) {
                    std::cout << "New client connected" << std::endl;
                    clientSockets.push_back(clientSocket);
                    FD_SET(clientSocket, &readFds);
                }
            }
            for (auto it = clientSockets.begin(); it != clientSockets.end(); ) {
                SOCKET clientSocket = *it;
                if (FD_ISSET(clientSocket, &tempFds)) {
                    char buffer[1024];
                    int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
                    if (bytesRead <= 0) {
                        // 客户端关闭连接
                        std::cout << "Client disconnected" << std::endl;
                        closesocket(clientSocket);
                        FD_CLR(clientSocket, &readFds);
                        it = clientSockets.erase(it);
                    } else {
                        buffer[bytesRead] = '\0';
                        std::cout << "Received from client: " << buffer << std::endl;
                        // 处理请求,这里简单回显
                        send(clientSocket, buffer, bytesRead, 0);
                        ++it;
                    }
                } else {
                    ++it;
                }
            }
        }
    }

    // 清理资源
    for (SOCKET clientSocket : clientSockets) {
        closesocket(clientSocket);
    }
    closesocket(listenSocket);
    WSACleanup();
    return 0;
}
  • 初始化部分

    • 在Windows下,通过 WSAStartup 初始化Winsock库。
    • 创建监听套接字 listenSocket,指定地址族为 AF_INET(IPv4),套接字类型为 SOCK_STREAM(TCP)。
    • 绑定套接字到指定的地址和端口。
    • 开始监听,设置最大挂起连接数为5。
  • select 循环部分

    • 初始化一个 fd_set 类型的 readFds 集合,并将监听套接字 listenSocket 添加到该集合中。
    • while (true) 循环中,首先复制 readFdstempFds,这是因为 select 函数会修改传入的 fd_set 集合。
    • 调用 select 函数,第一个参数为0(在Windows下无意义,在Linux下通常设置为最大文件描述符加1),第二个参数为 tempFds 用于检查可读事件,第三、四个参数为 nullptr 表示不关心可写和异常事件,最后一个参数为 nullptr 表示阻塞等待直到有事件发生。
    • 如果 select 返回错误(activity == SOCKET_ERROR),输出错误信息并退出循环。
    • 如果 select 返回有活动(activity > 0),首先检查监听套接字是否有可读事件(FD_ISSET(listenSocket, &tempFds)),如果有则表示有新的客户端连接,调用 accept 接受连接,并将新的客户端套接字添加到 clientSockets 向量和 readFds 集合中。
    • 遍历 clientSockets 向量,检查每个客户端套接字是否有可读事件。如果有,接收客户端发送的数据,根据接收结果处理客户端连接(关闭或处理请求并回显)。
  • 清理部分

    • 关闭所有客户端套接字和监听套接字,并在Windows下调用 WSACleanup 清理Winsock库资源。