MST

星途 面试题库

面试题:网络编程中libev实现TCP服务器基础

在使用libev实现高效的TCP服务器时,简述如何初始化一个libev事件循环以及添加一个TCP连接监听事件,写出关键代码片段并解释各部分作用。
24.0万 热度难度
后端开发网络编程

知识考点

AI 面试

面试题答案

一键面试
  1. 初始化libev事件循环

    • 在libev中,事件循环由struct ev_loop结构体管理。可以使用ev_loop_new()函数来创建一个新的事件循环。
    #include <ev.h>
    struct ev_loop *loop = ev_loop_new(0);
    if (!loop) {
        // 处理创建失败的情况,例如内存不足等
        perror("ev_loop_new");
        return -1;
    }
    
    • 解释:
      • #include <ev.h>:引入libev库的头文件,包含了使用libev所需的各种结构体、函数声明等。
      • struct ev_loop *loop = ev_loop_new(0);:调用ev_loop_new函数创建一个新的事件循环。参数0表示使用默认的事件循环配置。如果函数执行成功,会返回一个指向struct ev_loop的指针loop,后续对事件的操作都将基于这个事件循环。
      • if (!loop):检查事件循环是否创建成功。如果loopNULL,表示创建失败,通过perror("ev_loop_new")输出错误信息,这里错误信息会提示ev_loop_new函数调用失败的原因,例如“Out of memory”等,最后return -1表示程序以错误状态退出。
  2. 添加一个TCP连接监听事件

    • 需要使用ev_io结构体来表示I/O事件(这里是TCP监听事件),并使用ev_io_initev_io_start函数来初始化和启动这个事件。
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    
    void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents);
    
    int main() {
        int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (listen_fd < 0) {
            perror("socket");
            return -1;
        }
    
        struct sockaddr_in servaddr;
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(8888);
        servaddr.sin_addr.s_addr = INADDR_ANY;
    
        if (bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
            perror("bind");
            close(listen_fd);
            return -1;
        }
    
        if (listen(listen_fd, 128) < 0) {
            perror("listen");
            close(listen_fd);
            return -1;
        }
    
        struct ev_loop *loop = ev_loop_new(0);
        if (!loop) {
            perror("ev_loop_new");
            return -1;
        }
    
        struct ev_io listen_watcher;
        ev_io_init(&listen_watcher, accept_cb, listen_fd, EV_READ);
        ev_io_start(loop, &listen_watcher);
    
        ev_loop(loop, 0);
    
        ev_io_stop(loop, &listen_watcher);
        ev_loop_destroy(loop);
        close(listen_fd);
        return 0;
    }
    
    void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
        if (revents & EV_READ) {
            int client_fd = accept(w->fd, NULL, NULL);
            if (client_fd < 0) {
                perror("accept");
                return;
            }
            // 处理新连接,例如可以在这里创建新的I/O watcher来处理客户端数据读写
            close(client_fd);
        }
    }
    
    • 解释:
      • #include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>:这些头文件是进行TCP socket编程所需要的,分别提供了socket相关函数、地址转换函数以及文件描述符操作函数等。
      • int listen_fd = socket(AF_INET, SOCK_STREAM, 0);:创建一个TCP socket,AF_INET表示使用IPv4地址族,SOCK_STREAM表示使用TCP协议,0表示使用默认协议。如果创建失败,listen_fd会小于0,通过perror("socket")输出错误信息并return -1退出程序。
      • servaddr结构体的初始化:设置地址族为IPv4(sin_family = AF_INET),端口号为8888(sin_port = htons(8888)htons函数将主机字节序转换为网络字节序),绑定地址为任意地址(sin_addr.s_addr = INADDR_ANY)。
      • bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)):将socket绑定到指定的地址和端口。如果绑定失败,通过perror("bind")输出错误信息,关闭socket(close(listen_fd))并return -1退出程序。
      • listen(listen_fd, 128):将socket设置为监听状态,128是等待连接队列的最大长度。如果监听失败,同样输出错误信息,关闭socket并退出程序。
      • struct ev_loop *loop = ev_loop_new(0);:如上述初始化事件循环部分所解释,创建一个新的事件循环。
      • struct ev_io listen_watcher;:定义一个ev_io结构体变量listen_watcher,用于表示TCP监听的I/O事件。
      • ev_io_init(&listen_watcher, accept_cb, listen_fd, EV_READ);:初始化listen_watcher。第一个参数是指向ev_io结构体的指针;第二个参数accept_cb是事件发生时的回调函数;第三个参数listen_fd是要监听的文件描述符(这里是TCP监听socket的文件描述符);EV_READ表示监听读事件,即有新的连接请求时会触发该事件。
      • ev_io_start(loop, &listen_watcher);:启动listen_watcher,将这个I/O事件添加到事件循环loop中开始监听。
      • ev_loop(loop, 0);:启动事件循环,开始处理事件。0表示使用默认的事件循环模式。
      • ev_io_stop(loop, &listen_watcher);:停止listen_watcher,从事件循环中移除该I/O事件。
      • ev_loop_destroy(loop);:销毁事件循环,释放相关资源。
      • close(listen_fd);:关闭TCP监听socket。
      • accept_cb函数:当listen_watcher监听的读事件(新连接请求)发生时会调用这个回调函数。if (revents & EV_READ)检查是否是读事件发生。如果是,调用accept函数接受新连接,获取客户端socket的文件描述符client_fd。如果accept失败,输出错误信息并返回。在实际应用中,可以在获取到client_fd后创建新的I/O watcher来处理客户端的数据读写等操作,这里简单关闭了client_fd