MST

星途 面试题库

面试题:C语言实现复杂场景下多路复用跨平台适配

设想有一个分布式系统,部分节点运行在Linux系统上,部分运行在其他类Unix系统甚至Windows系统上,需要使用C语言通过多路复用技术实现跨平台的高效通信。这个系统中有多种类型的通信连接(如TCP、UDP),且要处理动态增减的连接数和高并发数据传输。描述你将如何设计一个通用的多路复用跨平台框架,涵盖从连接管理、事件分发到错误处理等各个方面,并给出关键代码片段和整体架构思路。
11.0万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

整体架构思路

  1. 连接管理
    • 使用数据结构(如链表或数组)来存储所有的连接。每个连接节点包含连接类型(TCP/UDP)、文件描述符(或类似概念,如Windows下的套接字句柄)、地址信息等。
    • 提供函数用于添加新连接和移除连接,以处理动态增减的连接数。
  2. 多路复用技术选择
    • 在Linux和类Unix系统上,使用selectpollepoll。对于Windows系统,使用selectWSAAsyncSelect。由于要跨平台,select是一个较为通用的选择,尽管它在高并发场景下性能不如epoll等。
  3. 事件分发
    • 当多路复用函数检测到有事件发生(如可读、可写、异常等),根据连接类型和事件类型调用相应的处理函数。可以通过函数指针表或类似机制实现。
  4. 错误处理
    • 对每一个系统调用(如创建套接字、绑定地址、多路复用操作等)进行错误检查。记录错误日志,根据错误类型采取相应措施,如关闭连接、重新尝试操作等。

关键代码片段

  1. 跨平台套接字创建
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>

// 创建套接字
int create_socket(int domain, int type, int protocol) {
#ifdef _WIN32
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        printf("WSAStartup failed: %d\n", WSAGetLastError());
        return -1;
    }
#endif
    int sockfd = socket(domain, type, protocol);
    if (sockfd == -1) {
#ifdef _WIN32
        printf("Socket creation failed: %d\n", WSAGetLastError());
#else
        perror("Socket creation failed");
#endif
    }
    return sockfd;
}
  1. 连接管理数据结构及操作
// 定义连接结构体
typedef struct Connection {
    int fd;
    int type; // 0 for TCP, 1 for UDP
    struct sockaddr_storage addr;
    socklen_t addrlen;
    struct Connection* next;
} Connection;

// 添加连接
void add_connection(Connection** head, int fd, int type, struct sockaddr_storage* addr, socklen_t addrlen) {
    Connection* new_conn = (Connection*)malloc(sizeof(Connection));
    new_conn->fd = fd;
    new_conn->type = type;
    new_conn->addr = *addr;
    new_conn->addrlen = addrlen;
    new_conn->next = *head;
    *head = new_conn;
}

// 移除连接
void remove_connection(Connection** head, int fd) {
    Connection* current = *head;
    Connection* prev = NULL;
    while (current != NULL && current->fd != fd) {
        prev = current;
        current = current->next;
    }
    if (current == NULL) return;
    if (prev == NULL) {
        *head = current->next;
    } else {
        prev->next = current->next;
    }
    free(current);
}
  1. 多路复用及事件分发
// 事件处理函数指针
typedef void (*EventHandler)(int fd, int event_type);

// 处理读事件
void handle_read(int fd, int event_type) {
    // 处理读数据逻辑
    char buffer[1024];
    ssize_t bytes_read;
#ifdef _WIN32
    bytes_read = recv(fd, buffer, sizeof(buffer), 0);
#else
    bytes_read = read(fd, buffer, sizeof(buffer));
#endif
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        printf("Received: %s\n", buffer);
    } else if (bytes_read == 0) {
        // 连接关闭
        printf("Connection closed\n");
    } else {
#ifdef _WIN32
        printf("Read error: %d\n", WSAGetLastError());
#else
        perror("Read error");
#endif
    }
}

// 多路复用及事件分发
void multiplex(Connection* head, EventHandler read_handler, EventHandler write_handler, EventHandler error_handler) {
#ifdef _WIN32
    fd_set read_fds, write_fds, error_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    FD_ZERO(&error_fds);
    int max_fd = 0;
#else
    fd_set read_fds, write_fds, error_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    FD_ZERO(&error_fds);
    int max_fd = 0;
    Connection* current = head;
    while (current != NULL) {
        if (current->fd > max_fd) {
            max_fd = current->fd;
        }
        FD_SET(current->fd, &read_fds);
        FD_SET(current->fd, &write_fds);
        FD_SET(current->fd, &error_fds);
        current = current->next;
    }
    int activity = select(max_fd + 1, &read_fds, &write_fds, &error_fds, NULL);
    if (activity < 0) {
#ifdef _WIN32
        printf("Select error: %d\n", WSAGetLastError());
#else
        perror("Select error");
#endif
    } else if (activity > 0) {
        current = head;
        while (current != NULL) {
            if (FD_ISSET(current->fd, &read_fds)) {
                read_handler(current->fd, 0);
            }
            if (FD_ISSET(current->fd, &write_fds)) {
                write_handler(current->fd, 1);
            }
            if (FD_ISSET(current->fd, &error_fds)) {
                error_handler(current->fd, 2);
            }
            current = current->next;
        }
    }
}

通过上述代码和架构思路,可以实现一个基本的跨平台多路复用通信框架,涵盖连接管理、事件分发和错误处理等方面。实际应用中,还需要进一步优化和完善,如采用更高效的多路复用技术(在Linux上使用epoll等),以及对内存管理等方面进行更细致的处理。