You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派中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");
      }
  }

问题根源

  1. SO_RCVTIMEO/SO_SNDTIMEO不控制connect()超时:这两个套接字选项仅对已连接状态下的send()、read()、recv()等I/O操作生效,完全不影响connect()的超时逻辑。connect()的超时由系统内核的TCP重试机制决定,若服务器端口未监听或主机不可达,系统会收到ICMP错误包,connect()会立即返回失败,不会等待你设置的5秒。
  2. 缺少connect()错误处理:代码中未检查Soccon的返回值,若connect()失败,后续的send()/read()会直接操作无效套接字,快速返回错误,看起来像是“立即超时”。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:37:37