最近在工作上上手了一个QT的项目,里面包含了一个tftp的服务器,上手后我发现在同时用服务器传输多个文件的时候,会造成所有文件都传输失败,并且客户端再进行传输就无响应了?
由于现象比较难复现,并且最开始我并不清除是什么原因导致的无响应,且一个客户端无响应后会导致所有客户端都开始无响应。我一度以为是ip冲突了,尝试了许多方法都难以定义。于是选择观察多个文件传输时内存的一个变化,发现一旦失败就会造成内存泄漏,且内存泄露的大小和要传输的文件大小一致。那么此时定位就逐渐明确了,大致范围就在服务器传输数据的时候,从本地写入内存后未成功发送,造成了泄露。
下断点观察服务器后,发现了原有的服务器采用了阻塞的socket,并且没有重传机制和超时时间。造成一直recv从而使服务器阻塞了。下图是我的服务器的大致流程。

什么是阻塞IO和非阻塞IO可以去看我的【UNIX网络编程】5种I/O模型这篇文章,讲解了阻塞IO相关的问题。下面给大家讲一下我针对于这个服务器的解决办法。
select + 重传机制
嵌入式中常用的IO复用模型就是select,可以用于为服务器指定超时时间,避免无限等待。select的具体使用方法可以去看【UNIX网络编程】I/O多路复用这篇文章。
下面是一个服务器的示例:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#define PORT 8080
#define BUFFER_SIZE 1024
#define MAX_RETRIES 3
#define TIMEOUT_SEC 5
#define MAX_CLIENTS 10
/* 函数声明 */
int createServerSocket(int port);
int acceptClientConnection(int serverFd);
int waitForDataWithTimeout(int sockFd, int timeoutSec);
int receiveWithRetry(int sockFd, char* buffer, int bufferSize, int maxRetries, int timeoutSec);
void handleClient(int clientSock);
/* 创建服务器socket并绑定端口 */
int createServerSocket(int port) {
int serverFd;
struct sockaddr_in address;
int opt = 1;
/* 创建socket */
if ((serverFd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
return -1;
}
/* 设置socket选项 */
if (setsockopt(serverFd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
close(serverFd);
return -1;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
/* 绑定端口 */
if (bind(serverFd, (struct sockaddr*)&address, sizeof(address)) < 0) {
perror("bind failed");
close(serverFd);
return -1;
}
/* 开始监听 */
if (listen(serverFd, MAX_CLIENTS) < 0) {
perror("listen");
close(serverFd);
return -1;
}
printf("Server listening on port %d\n", port);
return serverFd;
}
/* 接受客户端连接 */
int acceptClientConnection(int serverFd) {
struct sockaddr_in address;
int addrLen = sizeof(address);
int clientSock;
if ((clientSock = accept(serverFd, (struct sockaddr*)&address, (socklen_t*)&addrLen)) < 0) {
perror("accept");
return -1;
}
printf("Client connected from %s:%d\n",
inet_ntoa(address.sin_addr), ntohs(address.sin_port));
return clientSock;
}
/* 使用select等待数据到达,带超时功能
* 返回: 1-有数据可读, 0-超时, -1-错误 */
int waitForDataWithTimeout(int sockFd, int timeoutSec) {
fd_set readFds;
struct timeval tv;
FD_ZERO(&readFds);
FD_SET(sockFd, &readFds);
tv.tv_sec = timeoutSec;
tv.tv_usec = 0;
int result = select(sockFd + 1, &readFds, NULL, NULL, &tv);
if (result < 0) {
perror("select error");
return -1;
} else if (result == 0) {
return 0; /* 超时 */
}
return FD_ISSET(sockFd, &readFds) ? 1 : 0;
}
/* 带重传机制的数据接收函数 */
int receiveWithRetry(int sockFd, char* buffer, int bufferSize, int maxRetries, int timeoutSec) {
int retryCount = 0;
int bytesReceived;
while (retryCount < maxRetries) {
int ready = waitForDataWithTimeout(sockFd, timeoutSec);
if (ready == 1) {
bytesReceived = recv(sockFd, buffer, bufferSize - 1, 0);
if (bytesReceived < 0) {
perror("recv failed");
return -1;
} else if (bytesReceived == 0) {
printf("Connection closed by peer\n");
return 0;
} else {
buffer[bytesReceived] = '\0'; /* 确保字符串结束 */
return bytesReceived;
}
} else if (ready == 0) {
printf("Timeout, waiting for data... (%d/%d)\n", retryCount + 1, maxRetries);
retryCount++;
} else {
return -1; /* select错误 */
}
}
printf("Max retries exceeded for receiving data\n");
return -2; /* 超过最大重试次数 */
}
/* 处理客户端连接 */
void handleClient(int clientSock) {
char buffer[BUFFER_SIZE];
int bytesReceived;
while (1) {
memset(buffer, 0, BUFFER_SIZE);
bytesReceived = receiveWithRetry(clientSock, buffer, BUFFER_SIZE, MAX_RETRIES, TIMEOUT_SEC);
if (bytesReceived > 0) {
printf("Received: %s\n", buffer);
/* 发送确认 */
const char* ackMsg = "ACK";
if (send(clientSock, ackMsg, strlen(ackMsg), 0) < 0) {
perror("send ACK failed");
break;
}
printf("Sent ACK\n");
} else if (bytesReceived == 0) {
printf("Client disconnected\n");
break;
} else if (bytesReceived == -2) {
printf("Max retries exceeded, closing connection\n");
break;
} else {
perror("Receive error");
break;
}
}
}
/* 主函数 */
int main() {
int serverFd, clientSock;
serverFd = createServerSocket(PORT);
if (serverFd < 0) {
fprintf(stderr, "Failed to create server socket\n");
return EXIT_FAILURE;
}
clientSock = acceptClientConnection(serverFd);
if (clientSock < 0) {
fprintf(stderr, "Failed to accept client connection\n");
close(serverFd);
return EXIT_FAILURE;
}
handleClient(clientSock);
close(clientSock);
close(serverFd);
printf("Server shutdown\n");
return EXIT_SUCCESS;
}