面试题答案
一键面试整体架构思路
- 连接管理:
- 使用数据结构(如链表或数组)来存储所有的连接。每个连接节点包含连接类型(TCP/UDP)、文件描述符(或类似概念,如Windows下的套接字句柄)、地址信息等。
- 提供函数用于添加新连接和移除连接,以处理动态增减的连接数。
- 多路复用技术选择:
- 在Linux和类Unix系统上,使用
select
、poll
或epoll
。对于Windows系统,使用select
或WSAAsyncSelect
。由于要跨平台,select
是一个较为通用的选择,尽管它在高并发场景下性能不如epoll
等。
- 在Linux和类Unix系统上,使用
- 事件分发:
- 当多路复用函数检测到有事件发生(如可读、可写、异常等),根据连接类型和事件类型调用相应的处理函数。可以通过函数指针表或类似机制实现。
- 错误处理:
- 对每一个系统调用(如创建套接字、绑定地址、多路复用操作等)进行错误检查。记录错误日志,根据错误类型采取相应措施,如关闭连接、重新尝试操作等。
关键代码片段
- 跨平台套接字创建:
#ifdef _WIN32
#include <winsock2.h>
#include <windows.h>
#else
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif
#include <stdio.h>
#include <stdlib.h>
// 创建套接字
int create_socket(int domain, int type, int protocol) {
#ifdef _WIN32
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
printf("WSAStartup failed: %d\n", WSAGetLastError());
return -1;
}
#endif
int sockfd = socket(domain, type, protocol);
if (sockfd == -1) {
#ifdef _WIN32
printf("Socket creation failed: %d\n", WSAGetLastError());
#else
perror("Socket creation failed");
#endif
}
return sockfd;
}
- 连接管理数据结构及操作:
// 定义连接结构体
typedef struct Connection {
int fd;
int type; // 0 for TCP, 1 for UDP
struct sockaddr_storage addr;
socklen_t addrlen;
struct Connection* next;
} Connection;
// 添加连接
void add_connection(Connection** head, int fd, int type, struct sockaddr_storage* addr, socklen_t addrlen) {
Connection* new_conn = (Connection*)malloc(sizeof(Connection));
new_conn->fd = fd;
new_conn->type = type;
new_conn->addr = *addr;
new_conn->addrlen = addrlen;
new_conn->next = *head;
*head = new_conn;
}
// 移除连接
void remove_connection(Connection** head, int fd) {
Connection* current = *head;
Connection* prev = NULL;
while (current != NULL && current->fd != fd) {
prev = current;
current = current->next;
}
if (current == NULL) return;
if (prev == NULL) {
*head = current->next;
} else {
prev->next = current->next;
}
free(current);
}
- 多路复用及事件分发:
// 事件处理函数指针
typedef void (*EventHandler)(int fd, int event_type);
// 处理读事件
void handle_read(int fd, int event_type) {
// 处理读数据逻辑
char buffer[1024];
ssize_t bytes_read;
#ifdef _WIN32
bytes_read = recv(fd, buffer, sizeof(buffer), 0);
#else
bytes_read = read(fd, buffer, sizeof(buffer));
#endif
if (bytes_read > 0) {
buffer[bytes_read] = '\0';
printf("Received: %s\n", buffer);
} else if (bytes_read == 0) {
// 连接关闭
printf("Connection closed\n");
} else {
#ifdef _WIN32
printf("Read error: %d\n", WSAGetLastError());
#else
perror("Read error");
#endif
}
}
// 多路复用及事件分发
void multiplex(Connection* head, EventHandler read_handler, EventHandler write_handler, EventHandler error_handler) {
#ifdef _WIN32
fd_set read_fds, write_fds, error_fds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&error_fds);
int max_fd = 0;
#else
fd_set read_fds, write_fds, error_fds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_ZERO(&error_fds);
int max_fd = 0;
Connection* current = head;
while (current != NULL) {
if (current->fd > max_fd) {
max_fd = current->fd;
}
FD_SET(current->fd, &read_fds);
FD_SET(current->fd, &write_fds);
FD_SET(current->fd, &error_fds);
current = current->next;
}
int activity = select(max_fd + 1, &read_fds, &write_fds, &error_fds, NULL);
if (activity < 0) {
#ifdef _WIN32
printf("Select error: %d\n", WSAGetLastError());
#else
perror("Select error");
#endif
} else if (activity > 0) {
current = head;
while (current != NULL) {
if (FD_ISSET(current->fd, &read_fds)) {
read_handler(current->fd, 0);
}
if (FD_ISSET(current->fd, &write_fds)) {
write_handler(current->fd, 1);
}
if (FD_ISSET(current->fd, &error_fds)) {
error_handler(current->fd, 2);
}
current = current->next;
}
}
}
通过上述代码和架构思路,可以实现一个基本的跨平台多路复用通信框架,涵盖连接管理、事件分发和错误处理等方面。实际应用中,还需要进一步优化和完善,如采用更高效的多路复用技术(在Linux上使用epoll
等),以及对内存管理等方面进行更细致的处理。