面试题答案
一键面试1. libev事件驱动模型工作原理
libev采用基于事件驱动的I/O多路复用模型。其核心是一个事件循环(event loop),它持续监控一组文件描述符(如socket)上的事件,例如可读、可写或错误事件。
- 事件注册:应用程序将感兴趣的事件(如socket可读事件)与相应的回调函数注册到事件循环中。例如,当一个TCP socket有数据可读时,就会触发注册的可读事件。
- 事件监控:事件循环使用操作系统提供的I/O多路复用机制(如epoll、kqueue等,libev会根据不同操作系统选择最优机制)来高效地监控所有注册的文件描述符。这些机制允许程序在一个线程内同时监控多个文件描述符,而无需为每个文件描述符创建一个单独的线程或进程。
- 事件分发:当有事件发生时,事件循环会从监控的文件描述符集合中获取发生事件的文件描述符,并调用与该事件相关联的回调函数。应用程序在回调函数中处理具体的业务逻辑,例如读取socket数据、处理请求等。处理完事件后,事件循环继续监控文件描述符,等待下一个事件发生。
2. 利用libev库实现简单TCP服务器监听特定端口并处理客户端连接的示例代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ev.h>
#define PORT 8888
#define BACKLOG 10
// 全局的事件循环
struct ev_loop *loop;
// 用于监听socket的事件结构体
struct ev_io listen_watcher;
// 处理新连接的回调函数
void accept_cb(struct ev_loop *loop, struct ev_io *w, int revents) {
int client_socket = accept(w->fd, NULL, NULL);
if (client_socket == -1) {
perror("accept");
return;
}
printf("Accepted new connection: %d\n", client_socket);
// 这里可以进一步处理客户端连接,如创建新的事件监听客户端socket的读写事件等
close(client_socket);
}
int main() {
int listen_socket = socket(AF_INET, SOCK_STREAM, 0);
if (listen_socket == -1) {
perror("socket");
return 1;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
close(listen_socket);
return 1;
}
if (listen(listen_socket, BACKLOG) == -1) {
perror("listen");
close(listen_socket);
return 1;
}
loop = ev_default_loop(0);
// 初始化监听socket的事件
ev_io_init(&listen_watcher, accept_cb, listen_socket, EV_READ);
ev_io_start(loop, &listen_watcher);
printf("Server is listening on port %d...\n", PORT);
ev_run(loop, 0);
// 清理资源
ev_io_stop(loop, &listen_watcher);
close(listen_socket);
return 0;
}
在上述代码中:
- 首先创建一个TCP socket并绑定到指定端口,然后开始监听。
- 接着初始化libev的事件循环,并创建一个
ev_io
结构体用于监听监听socket的读事件(即有新连接到来)。 accept_cb
函数作为读事件的回调函数,在有新连接时被调用,负责接受新连接并进行简单处理(这里只是打印新连接的socket描述符,实际应用中可以进行更多业务逻辑处理,如创建新的事件监听客户端socket的读写等)。- 最后启动事件循环,程序进入事件驱动的运行模式,等待并处理事件。当事件循环结束后,清理资源并关闭监听socket。