MST

星途 面试题库

面试题:C语言同步多路复用在复杂场景下的设计与实现

设想一个复杂的网络应用场景,其中涉及多种不同类型的I/O事件(如TCP连接、UDP数据接收、文件描述符监控等),需要使用C语言的同步多路复用技术来实现高效的事件处理。请设计一个整体架构,并详细描述关键的数据结构和函数实现,同时要考虑到性能优化、资源管理以及错误处理等方面。
28.2万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

整体架构设计

  1. 事件循环:采用一个主循环来不断监听和处理各种I/O事件。在循环内,使用同步多路复用技术(如select、poll或epoll)来等待事件的发生。
  2. 事件注册与管理:提供接口用于注册、删除和修改感兴趣的I/O事件。这些事件信息将被传递给多路复用函数。
  3. 事件处理回调:为每种类型的I/O事件定义相应的回调函数,当事件发生时,调用对应的回调函数进行处理。

关键数据结构

  1. 事件结构体
typedef struct {
    int fd; // 文件描述符
    short events; // 感兴趣的事件类型(如POLLIN、POLLOUT等)
    void (*callback)(int fd, short events, void *arg); // 事件发生时的回调函数
    void *arg; // 传递给回调函数的参数
} Event;
  1. 事件集合结构体
typedef struct {
    Event *events;
    int capacity;
    int count;
} EventSet;
  1. 初始化事件集合函数
void initEventSet(EventSet *set, int initialCapacity) {
    set->events = (Event *)malloc(initialCapacity * sizeof(Event));
    set->capacity = initialCapacity;
    set->count = 0;
}
  1. 添加事件到集合函数
void addEvent(EventSet *set, int fd, short events, void (*callback)(int fd, short events, void *arg), void *arg) {
    if (set->count >= set->capacity) {
        set->capacity *= 2;
        set->events = (Event *)realloc(set->events, set->capacity * sizeof(Event));
    }
    set->events[set->count].fd = fd;
    set->events[set->count].events = events;
    set->events[set->count].callback = callback;
    set->events[set->count].arg = arg;
    set->count++;
}
  1. 删除事件从集合函数
void removeEvent(EventSet *set, int fd) {
    for (int i = 0; i < set->count; i++) {
        if (set->events[i].fd == fd) {
            set->events[i] = set->events[set->count - 1];
            set->count--;
            break;
        }
    }
}

关键函数实现

  1. 基于poll的事件循环
void eventLoop(EventSet *set) {
    struct pollfd *pollfds = (struct pollfd *)malloc(set->count * sizeof(struct pollfd));
    for (int i = 0; i < set->count; i++) {
        pollfds[i].fd = set->events[i].fd;
        pollfds[i].events = set->events[i].events;
        pollfds[i].revents = 0;
    }
    while (1) {
        int nfds = poll(pollfds, set->count, -1);
        if (nfds == -1) {
            perror("poll");
            break;
        } else if (nfds > 0) {
            for (int i = 0; i < set->count; i++) {
                if (pollfds[i].revents) {
                    set->events[i].callback(pollfds[i].fd, pollfds[i].revents, set->events[i].arg);
                }
            }
        }
    }
    free(pollfds);
}
  1. 示例回调函数
void tcpConnectionCallback(int fd, short events, void *arg) {
    if (events & POLLIN) {
        // 处理TCP连接上的读事件
        char buffer[1024];
        ssize_t n = recv(fd, buffer, sizeof(buffer), 0);
        if (n == -1) {
            perror("recv");
        } else if (n == 0) {
            // 连接关闭
            close(fd);
        } else {
            buffer[n] = '\0';
            printf("Received: %s\n", buffer);
        }
    }
}

性能优化

  1. 减少系统调用次数:尽量批量处理事件,避免频繁调用多路复用函数。
  2. 高效的数据结构:使用合适的数据结构(如哈希表)来快速查找和管理事件,减少遍历时间。
  3. 非阻塞I/O:对于I/O操作,采用非阻塞模式,避免I/O操作时阻塞事件循环。

资源管理

  1. 内存管理:及时释放不再使用的内存,如在删除事件时释放相关资源。
  2. 文件描述符管理:确保在不再使用文件描述符时关闭它们,避免文件描述符泄漏。

错误处理

  1. 系统调用错误:在每次系统调用(如poll、recv、close等)后检查返回值,及时处理错误并记录日志。
  2. 内存分配错误:在内存分配(如malloc、realloc)失败时,及时处理错误,如输出错误信息并终止程序或进行合理的降级处理。