面试题答案
一键面试1. 非阻塞I/O与事件驱动模型结合实现多客户端连接处理
- 设置非阻塞套接字:
在创建套接字后,使用
fcntl
函数将套接字设置为非阻塞模式。例如:int sockfd = socket(AF_INET, SOCK_STREAM, 0); int flags = fcntl(sockfd, F_GETFL, 0); fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
- 事件驱动模型 - 使用
epoll
(以epoll
为例,select
和poll
也类似原理):- 创建
epoll
实例:int epollfd = epoll_create1(0); if (epollfd == -1) { perror("epoll_create1"); exit(EXIT_FAILURE); }
- 添加监听套接字到
epoll
实例:struct epoll_event event; event.data.fd = sockfd; event.events = EPOLLIN | EPOLLET; // 边缘触发模式,也可以用水平触发EPOLLIN if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) == -1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); }
- 事件循环:
struct epoll_event events[10]; // 一次最多处理10个事件 while (1) { int n = epoll_wait(epollfd, events, 10, -1); for (int i = 0; i < n; ++i) { if (events[i].data.fd == sockfd) { // 处理新连接 struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); int clientfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len); if (clientfd == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("accept"); } continue; } // 将新客户端套接字设置为非阻塞 int client_flags = fcntl(clientfd, F_GETFL, 0); fcntl(clientfd, F_SETFL, client_flags | O_NONBLOCK); // 将新客户端套接字添加到epoll实例 event.data.fd = clientfd; event.events = EPOLLIN | EPOLLET; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &event) == -1) { perror("epoll_ctl: client_sock"); close(clientfd); } } else { // 处理客户端数据 int clientfd = events[i].data.fd; char buffer[1024]; ssize_t nread = read(clientfd, buffer, sizeof(buffer)); if (nread == -1) { if (errno != EAGAIN && errno != EWOULDBLOCK) { perror("read"); // 错误处理,例如从epoll中移除并关闭套接字 epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, NULL); close(clientfd); } } else if (nread == 0) { // 客户端关闭连接 epoll_ctl(epollfd, EPOLL_CTL_DEL, clientfd, NULL); close(clientfd); } else { // 处理读取到的数据 // 例如回显数据 write(clientfd, buffer, nread); } } } }
- 创建
2. 优势
- 提高并发性:
- 传统阻塞I/O模型在处理一个客户端连接时,若该客户端没有数据发送,服务器会阻塞在
read
或accept
等函数上,无法处理其他客户端连接。而非阻塞I/O与事件驱动模型结合,服务器可以在等待数据到达时,继续处理其他有事件发生的客户端连接,大大提高了服务器处理多个并发连接的能力。
- 传统阻塞I/O模型在处理一个客户端连接时,若该客户端没有数据发送,服务器会阻塞在
- 资源利用更高效:
- 传统阻塞I/O模型可能会因为阻塞而浪费大量CPU时间片。在非阻塞I/O与事件驱动模型下,只有当有事件发生时(如可读、可写等),才会进行相应处理,CPU资源不会被无效地阻塞等待,提高了资源利用率。
- 响应更及时:
- 对于多个客户端连接,非阻塞I/O与事件驱动模型能够及时响应每个客户端的事件,不会因为某个客户端的长时间无操作而影响其他客户端的交互。例如,当一个客户端发送数据时,服务器能迅速检测到并进行处理,而不像传统阻塞I/O模型那样,可能会因为其他客户端的阻塞操作而延迟响应。