如何避免在无响应服务器上调用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
相关产品推荐
相关产品推荐

