MST

星途 面试题库

面试题:C++ 同步多路复用跨平台及性能调优

在跨Windows和Linux平台的C++ 项目中,使用同步多路复用技术实现一个高性能的网络服务器。需要考虑不同平台下多路复用机制(如Windows的WSAAsyncSelect和Linux的epoll)的差异,并进行性能调优。请详细阐述设计思路,包括如何处理连接管理、数据收发以及异常情况,同时给出主要的跨平台代码框架。
49.0万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 多路复用机制选择
    • 在Windows平台,使用WSAAsyncSelect模型。它基于消息驱动,通过窗口接收网络事件通知。
    • 在Linux平台,使用epollepoll具有高效的事件通知机制,支持大量并发连接。
  2. 连接管理
    • 监听连接:在Windows和Linux上都创建监听套接字,绑定到指定端口并开始监听。
    • 新连接处理:当有新连接事件触发(Windows通过WSAAsyncSelect消息,Linux通过epoll事件),接受新连接并将其加入连接管理列表。可以使用一个std::vectorstd::unordered_map来存储连接套接字和相关信息。
  3. 数据收发
    • 发送数据:为每个连接维护一个发送缓冲区。当需要发送数据时,将数据拷贝到发送缓冲区,然后根据多路复用机制的事件通知,在套接字可写时将数据发送出去。
    • 接收数据:同样为每个连接维护一个接收缓冲区。当有数据可读事件触发,将数据读取到接收缓冲区,之后进行数据解析和处理。
  4. 异常情况处理
    • 连接断开:无论是Windows还是Linux,当检测到连接断开事件(如WSAECONNRESET错误或epoll返回的事件类型表示连接关闭),从连接管理列表中移除该连接,并释放相关资源。
    • 其他错误:对于其他网络错误,如发送或接收数据失败,根据错误码进行相应处理,如记录日志、尝试重连等。

主要跨平台代码框架

#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#include <ws2tcpip.h>
#include <iostream>
#include <vector>
#include <unordered_map>
#pragma comment(lib, "ws2_32.lib")
// 自定义消息用于WSAAsyncSelect
#define WM_SOCKET WM_USER + 1

// 连接信息结构体
struct Connection {
    SOCKET sock;
    // 其他连接相关信息,如发送/接收缓冲区
};

// 全局变量
std::unordered_map<SOCKET, Connection> connections;
HWND hwnd;

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int main() {
    // 初始化Winsock
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        std::cerr << "WSAStartup failed: " << WSAGetLastError() << std::endl;
        return 1;
    }

    // 创建窗口类
    WNDCLASS wc = {0};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = GetModuleHandle(nullptr);
    wc.lpszClassName = L"SocketServer";
    RegisterClass(&wc);

    // 创建窗口
    hwnd = CreateWindow(wc.lpszClassName, L"Socket Server", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        nullptr, nullptr, wc.hInstance, nullptr);

    if (hwnd == nullptr) {
        std::cerr << "CreateWindow failed: " << GetLastError() << std::endl;
        WSACleanup();
        return 1;
    }

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

    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons(54000);
    inet_pton(AF_INET, "0.0.0.0", &hint.sin_addr);

    if (bind(listenSocket, (sockaddr*)&hint, sizeof(hint)) == SOCKET_ERROR) {
        std::cerr << "bind failed: " << WSAGetLastError() << std::endl;
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }

    if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
        std::cerr << "listen failed: " << WSAGetLastError() << std::endl;
        closesocket(listenSocket);
        WSACleanup();
        return 1;
    }

    // 关联消息和套接字
    WSAAsyncSelect(listenSocket, hwnd, WM_SOCKET, FD_ACCEPT | FD_CLOSE);

    ShowWindow(hwnd, SW_SHOW);
    UpdateWindow(hwnd);

    // 消息循环
    MSG msg = {0};
    while (GetMessage(&msg, nullptr, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // 清理资源
    for (auto& conn : connections) {
        closesocket(conn.first);
    }
    closesocket(listenSocket);
    WSACleanup();
    return 0;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_SOCKET:
        if (WSAGETSELECTERROR(lParam)) {
            std::cerr << "Socket error: " << WSAGETSELECTERROR(lParam) << std::endl;
            SOCKET sock = (SOCKET)wParam;
            if (connections.find(sock) != connections.end()) {
                closesocket(sock);
                connections.erase(sock);
            }
        }
        else {
            switch (WSAGETSELECTEVENT(lParam)) {
            case FD_ACCEPT:
                {
                    SOCKET listenSocket = (SOCKET)wParam;
                    SOCKET clientSocket = accept(listenSocket, nullptr, nullptr);
                    if (clientSocket != INVALID_SOCKET) {
                        Connection newConn;
                        newConn.sock = clientSocket;
                        connections[clientSocket] = newConn;
                        WSAAsyncSelect(clientSocket, hwnd, WM_SOCKET, FD_READ | FD_WRITE | FD_CLOSE);
                    }
                }
                break;
            case FD_READ:
                {
                    SOCKET clientSocket = (SOCKET)wParam;
                    char buffer[1024];
                    int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
                    if (bytesRead > 0) {
                        buffer[bytesRead] = '\0';
                        std::cout << "Received: " << buffer << std::endl;
                    }
                    else if (bytesRead == 0) {
                        std::cout << "Connection closed" << std::endl;
                        closesocket(clientSocket);
                        connections.erase(clientSocket);
                    }
                    else {
                        std::cerr << "recv failed: " << WSAGetLastError() << std::endl;
                        closesocket(clientSocket);
                        connections.erase(clientSocket);
                    }
                }
                break;
            case FD_WRITE:
                // 发送数据逻辑
                break;
            case FD_CLOSE:
                {
                    SOCKET clientSocket = (SOCKET)wParam;
                    std::cout << "Connection closed" << std::endl;
                    closesocket(clientSocket);
                    connections.erase(clientSocket);
                }
                break;
            }
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}
#else
#include <iostream>
#include <vector>
#include <unordered_map>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>

// 连接信息结构体
struct Connection {
    int sock;
    // 其他连接相关信息,如发送/接收缓冲区
};

// 全局变量
std::unordered_map<int, Connection> connections;
const int MAX_EVENTS = 10;

void setNonblocking(int sock) {
    int flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);
}

int main() {
    // 创建监听套接字
    int listenSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSocket == -1) {
        std::cerr << "socket failed" << std::endl;
        return 1;
    }

    sockaddr_in hint;
    hint.sin_family = AF_INET;
    hint.sin_port = htons(54000);
    hint.sin_addr.s_addr = INADDR_ANY;

    if (bind(listenSocket, (sockaddr*)&hint, sizeof(hint)) == -1) {
        std::cerr << "bind failed" << std::endl;
        close(listenSocket);
        return 1;
    }

    if (listen(listenSocket, SOMAXCONN) == -1) {
        std::cerr << "listen failed" << std::endl;
        close(listenSocket);
        return 1;
    }

    setNonblocking(listenSocket);

    int epollFd = epoll_create1(0);
    if (epollFd == -1) {
        std::cerr << "epoll_create1 failed" << std::endl;
        close(listenSocket);
        return 1;
    }

    epoll_event event;
    event.data.fd = listenSocket;
    event.events = EPOLLIN;
    if (epoll_ctl(epollFd, EPOLL_CTL_ADD, listenSocket, &event) == -1) {
        std::cerr << "epoll_ctl add listen socket failed" << std::endl;
        close(listenSocket);
        close(epollFd);
        return 1;
    }

    epoll_event events[MAX_EVENTS];
    while (true) {
        int numEvents = epoll_wait(epollFd, events, MAX_EVENTS, -1);
        if (numEvents == -1) {
            std::cerr << "epoll_wait failed" << std::endl;
            break;
        }

        for (int i = 0; i < numEvents; ++i) {
            if (events[i].data.fd == listenSocket) {
                int clientSocket = accept(listenSocket, nullptr, nullptr);
                if (clientSocket != -1) {
                    setNonblocking(clientSocket);
                    Connection newConn;
                    newConn.sock = clientSocket;
                    connections[clientSocket] = newConn;

                    event.data.fd = clientSocket;
                    event.events = EPOLLIN | EPOLLOUT | EPOLLET;
                    if (epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &event) == -1) {
                        std::cerr << "epoll_ctl add client socket failed" << std::endl;
                        close(clientSocket);
                    }
                }
            }
            else {
                int clientSocket = events[i].data.fd;
                if (events[i].events & EPOLLIN) {
                    char buffer[1024];
                    int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
                    if (bytesRead > 0) {
                        buffer[bytesRead] = '\0';
                        std::cout << "Received: " << buffer << std::endl;
                    }
                    else if (bytesRead == 0) {
                        std::cout << "Connection closed" << std::endl;
                        close(clientSocket);
                        connections.erase(clientSocket);
                        epoll_ctl(epollFd, EPOLL_CTL_DEL, clientSocket, nullptr);
                    }
                    else {
                        std::cerr << "recv failed" << std::endl;
                        close(clientSocket);
                        connections.erase(clientSocket);
                        epoll_ctl(epollFd, EPOLL_CTL_DEL, clientSocket, nullptr);
                    }
                }
                if (events[i].events & EPOLLOUT) {
                    // 发送数据逻辑
                }
            }
        }
    }

    // 清理资源
    for (auto& conn : connections) {
        close(conn.first);
    }
    close(listenSocket);
    close(epollFd);
    return 0;
}
#endif

上述代码展示了在Windows和Linux平台下,使用同步多路复用技术实现网络服务器的基本框架,涵盖了连接管理、数据收发及异常处理等方面。在实际应用中,还需根据具体需求进一步完善和优化,如缓冲区管理、性能调优等。