树莓派中setsockopt设置超时未正常工作问题排查
树莓派Bookworm 64位系统下TCP客户端套接字超时设置不生效问题
我在搭载Bookworm 64位系统的树莓派4上开发TCP客户端,需要在服务器无响应时设置至少2秒的套接字超时。通过C语言调用setsockopt()设置了SO_RCVTIMEO和SO_SNDTIMEO为5秒,但服务器无响应时程序仅数毫秒就触发超时,未达到预期延迟。
代码如下:
int sockfd,Soccon,DataIn; struct sockaddr_in server; struct timeval tv; char GpmsData[4000],Gpms[20]; sockfd=socket(AF_INET,SOCK_STREAM,0); server.sin_addr.s_addr = inet_addr("192.168.0.148"); server.sin_family=AF_INET; server.sin_port=htons(30000); tv.tv_sec= 5; // 5 seconds delay tv.tv_usec=0; setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,(const char *)&tv, sizeof(tv)); setsockopt(sockfd,SOL_SOCKET,SO_SNDTIMEO,(const char *)&tv, sizeof(tv)); Soccon=connect(sockfd,(struct sockaddr *)&server,sizeof(server)) ; Gpms[0]=0x01; Gpms[1]=0x03; Gpms[2]=0x02; Gpms[3]=0x00; Gpms[4]=0x03; Gpms[5]=0x00; Gpms[6]=0x05; if(send(sockfd,Gpms,7,0)<0) { printf("Data Sending Problem\n"); } else { printf("Data Sended\n"); DataIn=read(sockfd,GpmsData,3800); if(DataIn<0) { printf("Data Not Received\n"); } }
问题根源
SO_RCVTIMEO/SO_SNDTIMEO不控制connect()超时:这两个套接字选项仅对已连接状态下的send()、read()、recv()等I/O操作生效,完全不影响connect()的超时逻辑。connect()的超时由系统内核的TCP重试机制决定,若服务器端口未监听或主机不可达,系统会收到ICMP错误包,connect()会立即返回失败,不会等待你设置的5秒。- 缺少
connect()错误处理:代码中未检查Soccon的返回值,若connect()失败,后续的send()/read()会直接操作无效套接字,快速返回错误,看起来像是“立即超时”。 - I/O超时依赖有效连接状态:即使
connect()成功,若服务器只是不返回数据而非断开连接,你的超时设置本应生效,但前提是connect()已成功建立连接。
解决方案
要实现完整的超时控制,需要分别处理connect()阶段的自定义超时,以及已连接后的I/O操作超时:
1. 实现带超时的connect()
将套接字设为非阻塞模式,结合select()实现自定义超时的连接:
int connect_with_timeout(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_sec) { // 设置套接字为非阻塞 int flags = fcntl(sockfd, F_GETFL, 0); if (flags == -1) return -1; if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) return -1; // 发起非阻塞connect int ret = connect(sockfd, addr, addrlen); if (ret == 0) { // 连接立即成功,恢复阻塞模式 fcntl(sockfd, F_SETFL, flags); return 0; } else if (errno != EINPROGRESS) { // 连接立即失败 return -1; } // 使用select等待连接完成或超时 fd_set write_fds; FD_ZERO(&write_fds); FD_SET(sockfd, &write_fds); struct timeval tv = {timeout_sec, 0}; ret = select(sockfd + 1, NULL, &write_fds, NULL, &tv); if (ret == -1) { // select出错 close(sockfd); return -1; } else if (ret == 0) { // 超时 close(sockfd); errno = ETIMEDOUT; return -1; } // 检查连接是否成功建立 int error = 0; socklen_t len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) { close(sockfd); return -1; } if (error != 0) { close(sockfd); errno = error; return -1; } // 恢复套接字为阻塞模式 fcntl(sockfd, F_SETFL, flags); return 0; }
2. 修正主代码逻辑
整合带超时的connect(),并完善错误处理:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> // 带超时的connect函数 int connect_with_timeout(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int timeout_sec) { int flags = fcntl(sockfd, F_GETFL, 0); if (flags == -1) return -1; if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) return -1; int ret = connect(sockfd, addr, addrlen); if (ret == 0) { fcntl(sockfd, F_SETFL, flags); return 0; } else if (errno != EINPROGRESS) { return -1; } fd_set write_fds; FD_ZERO(&write_fds); FD_SET(sockfd, &write_fds); struct timeval tv = {timeout_sec, 0}; ret = select(sockfd + 1, NULL, &write_fds, NULL, &tv); if (ret == -1) { close(sockfd); return -1; } else if (ret == 0) { close(sockfd); errno = ETIMEDOUT; return -1; } int error = 0; socklen_t len = sizeof(error); if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) { close(sockfd); return -1; } if (error != 0) { close(sockfd); errno = error; return -1; } fcntl(sockfd, F_SETFL, flags); return 0; } int main() { int sockfd, DataIn; struct sockaddr_in server; struct timeval tv; char GpmsData[4000], Gpms[20]; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket creation failed"); exit(EXIT_FAILURE); } server.sin_addr.s_addr = inet_addr("192.168.0.148"); server.sin_family = AF_INET; server.sin_port = htons(30000); // 设置I/O操作超时(send/read) tv.tv_sec = 5; tv.tv_usec = 0; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) == -1) { perror("setsockopt SO_RCVTIMEO failed"); close(sockfd); exit(EXIT_FAILURE); } if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) == -1) { perror("setsockopt SO_SNDTIMEO failed"); close(sockfd); exit(EXIT_FAILURE); } // 使用带超时的connect,设置5秒超时 if (connect_with_timeout(sockfd, (struct sockaddr *)&server, sizeof(server), 5) == -1) { if (errno == ETIMEDOUT) { printf("Connect timed out after 5 seconds\n"); } else { perror("connect failed"); } close(sockfd); exit(EXIT_FAILURE); } printf("Connected to server\n"); // 构造发送数据 Gpms[0] = 0x01; Gpms[1] = 0x03; Gpms[2] = 0x02; Gpms[3] = 0x00; Gpms[4] = 0x03; Gpms[5] = 0x00; Gpms[6] = 0x05; if (send(sockfd, Gpms, 7, 0) < 0) { perror("Data Sending Problem"); } else { printf("Data Sent\n"); DataIn = read(sockfd, GpmsData, 3800); if (DataIn < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { printf("Read timed out after 5 seconds\n"); } else { perror("Data Not Received"); } } else { printf("Received %d bytes of data\n", DataIn); } } close(sockfd); return 0; }
关键说明
- 非阻塞connect+select:实现了
connect()阶段的自定义超时,避免系统默认的过短或过长超时。 - 恢复阻塞模式:完成连接后将套接字恢复为阻塞模式,不影响后续的
send()/read()操作。 - 完善错误判断:区分超时错误和其他网络错误,便于排查问题。
- I/O超时生效:
SO_RCVTIMEO和SO_SNDTIMEO仅在套接字已连接且处于有效状态时生效,此时read()会等待设置的5秒后再返回超时。
内容的提问来源于stack exchange,提问作者Sathish
相关产品推荐
相关产品推荐

