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

如何避免在无响应服务器上调用connect()时程序阻塞

问题描述

我尝试连接1.1.1.1的80和800端口,原本预期1.1.1.1:800的connect()调用会直接失败,但程序却在这个调用上完全陷入阻塞。我试过用SOCK_NONBLOCK标志,但这类小型应用里搞异步操作总觉得没必要。

原代码

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>

void foo(uint16_t port) {
  printf("Starting for %d...\n", port);
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  
  struct sockaddr_in sockaddr_in;
  sockaddr_in.sin_family = AF_INET;
  sockaddr_in.sin_addr.s_addr = inet_addr("1.1.1.1");
  sockaddr_in.sin_port = htons(port);
  
  printf(
    "connect: %d\n",
    connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in))
  );

  printf("Errno: %d\n", errno);

  close(sockfd);
}

int main() {
  foo(80);
  foo(800);
}

程序输出

Starting for 80...
connect: 0
Errno: 0
Starting for 800...

问题原因

TCP的connect()调用在默认阻塞模式下,会一直等待对方响应:如果目标端口没有监听,对方会返回RST包,connect()会立即失败;但如果网络中出现丢包(比如防火墙拦截了包且未返回RST),connect()会触发TCP重传机制,持续尝试连接直到超时(通常几十秒),这就导致了程序阻塞。

解决办法

不需要复杂的异步IO,给阻塞socket设置连接超时即可,以下两种简单方案可选:

方案1:用alarm()设置信号超时

在connect()前设置闹钟信号,超时后中断系统调用,实现简单:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <signal.h>

void sigalrm_handler(int sig) {
    // 空处理,仅用于中断connect
}

void foo(uint16_t port) {
    printf("Starting for %d...\n", port);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    struct sockaddr_in sockaddr_in;
    sockaddr_in.sin_family = AF_INET;
    sockaddr_in.sin_addr.s_addr = inet_addr("1.1.1.1");
    sockaddr_in.sin_port = htons(port);

    // 注册信号并设置3秒超时
    signal(SIGALRM, sigalrm_handler);
    alarm(3);

    int ret = connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in));
    alarm(0); // 取消闹钟

    printf("connect: %d\n", ret);
    printf("Errno: %d\n", errno);

    close(sockfd);
}

int main() {
    foo(80);
    foo(800);
}

注意:该方案会使用全局SIGALRM信号,若程序其他逻辑也用到该信号会产生冲突。

方案2:用select()实现超时

临时将socket设为非阻塞,调用connect()后用select()等待可写并设置超时,无信号冲突问题:

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/select.h>

void foo(uint16_t port) {
    printf("Starting for %d...\n", port);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    // 临时设置非阻塞
    int flags = fcntl(sockfd, F_GETFL, 0);
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    
    struct sockaddr_in sockaddr_in;
    sockaddr_in.sin_family = AF_INET;
    sockaddr_in.sin_addr.s_addr = inet_addr("1.1.1.1");
    sockaddr_in.sin_port = htons(port);

    int ret = connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in));
    int err = errno;

    if (ret == -1 && err == EINPROGRESS) {
        // 连接正在进行,用select等待3秒超时
        fd_set wfds;
        FD_ZERO(&wfds);
        FD_SET(sockfd, &wfds);

        struct timeval timeout = {3, 0};
        ret = select(sockfd + 1, NULL, &wfds, NULL, &timeout);

        if (ret == 0) {
            // 超时
            printf("connect: -1 (timeout)\n");
            err = ETIMEDOUT;
        } else if (ret > 0) {
            // 检查连接是否成功
            int optval;
            socklen_t optlen = sizeof(optval);
            getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &optval, &optlen);
            if (optval == 0) {
                ret = 0;
                err = 0;
            } else {
                ret = -1;
                err = optval;
            }
        } else {
            // select调用出错
            err = errno;
        }
    }

    // 恢复原socket模式(可选)
    fcntl(sockfd, F_SETFL, flags);

    printf("connect: %d\n", ret);
    printf("Errno: %d\n", err);

    close(sockfd);
}

int main() {
    foo(80);
    foo(800);
}

该方案更灵活,适合大多数小型应用场景,无需复杂异步逻辑。


内容的提问来源于stack exchange,提问作者Hitsuki

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 17:14:52