MST

星途 面试题库

面试题:C语言Linux Socket高并发连接管理策略

假设你需要在Linux环境下,使用C语言开发一个支持高并发Socket连接的服务器程序。阐述你会采用哪些技术和策略来管理这些连接,以确保服务器的高效运行,并且举例说明如何使用epoll机制实现这一目标。
32.0万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

管理高并发Socket连接的技术和策略

  1. I/O复用:使用selectpollepoll等I/O复用机制,能够在一个线程中同时监控多个Socket的状态变化,避免为每个连接创建单独的线程或进程,减少资源消耗。
  2. 线程池或进程池:预先创建一定数量的线程或进程,当有新连接到来时,从池中分配一个线程或进程处理,处理完毕后再放回池中,减少线程或进程创建销毁的开销。
  3. 事件驱动编程模型:基于事件(如连接建立、数据可读、数据可写等)来驱动程序的执行逻辑,提高程序的响应性和灵活性。
  4. 非阻塞I/O:将Socket设置为非阻塞模式,使得在执行I/O操作(如readwrite)时,不会因为没有数据而阻塞线程,提高系统的并发处理能力。

使用epoll机制实现高并发Socket服务器示例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10
#define BUF_SIZE 1024

int main(int argc, char *argv[]) {
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;
    char buf[BUF_SIZE];

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr));
    listen(serv_sock, 5);

    epfd = epoll_create(MAX_EVENTS);
    ep_events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * MAX_EVENTS);

    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while (1) {
        event_cnt = epoll_wait(epfd, ep_events, MAX_EVENTS, -1);
        if (event_cnt == -1) {
            perror("epoll_wait error");
            break;
        }

        for (int i = 0; i < event_cnt; i++) {
            if (ep_events[i].data.fd == serv_sock) {
                clnt_adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);

                event.events = EPOLLIN | EPOLLET;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf("connected client: %d \n", clnt_sock);
            } else {
                int fd = ep_events[i].data.fd;
                int str_len = read(fd, buf, BUF_SIZE - 1);
                if (str_len == 0) {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                    close(fd);
                    printf("closed client: %d \n", fd);
                } else {
                    buf[str_len] = 0;
                    printf("message from client: %s", buf);
                    write(fd, buf, str_len);
                }
            }
        }
    }
    close(serv_sock);
    close(epfd);
    free(ep_events);
    return 0;
}
  1. 创建Socket并绑定监听:使用socket函数创建TCP套接字,bind函数绑定IP地址和端口,listen函数开始监听连接。
  2. 创建epoll实例:使用epoll_create创建一个epoll实例,并返回一个文件描述符epfd
  3. 添加监听Socket到epoll:构造一个epoll_event结构体,设置监听Socket的事件为EPOLLIN(可读事件),并使用epoll_ctl函数将其添加到epoll实例中。
  4. 进入事件循环:通过epoll_wait函数等待事件发生,epoll_wait会阻塞,直到有事件发生或者超时。
  5. 处理新连接:当epoll_wait返回且事件对应的文件描述符是监听Socket时,调用accept函数接受新连接,并将新连接的Socket也添加到epoll实例中,同时设置其事件为EPOLLIN(可读事件)和EPOLLET(边缘触发模式,提高效率)。
  6. 处理客户端数据:当事件对应的文件描述符是客户端Socket时,调用read函数读取数据,若读取到0,表示客户端关闭连接,从epoll实例中删除该Socket并关闭;否则将读取到的数据回显给客户端。