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

非阻塞TCP连接中printf("%m")与errno值不符的问题排查

非阻塞TCP Connect的errno异常与连接状态判断

兄弟,这事儿我在老uclibc和2.6内核的环境下碰过好几次,确实挺坑的。咱们先拆解你遇到的两个问题:为啥errno是0但printf("%m")能输出正确的错误信息,以及怎么准确判断非阻塞连接的状态。

一、errno为0但%m输出正确的原因

你碰到的这个矛盾现象,核心问题出在老版本uclibc与2.6早期内核的兼容性差异上:

  • 在标准glibc环境中,非阻塞connect返回-1时,内核会把EINPROGRESS(对应错误信息"Operation now in progress")正确设置到用户空间的errno里,printf("%m")就是靠读取errno来输出错误信息的。
  • 但老版本的uclibc(比如0.9.x系列)在处理非阻塞connect时存在实现差异:它并没有把EINPROGRESS同步到用户可见的errno变量中,却在printf("%m")的实现里做了特殊处理——直接通过getsockopt(SO_ERROR)读取了socket内部的错误状态,而非依赖全局的errno。这就导致了你看到的:errno显示0,但%m能输出正确的连接中提示。

而且你提到确认过printf没修改errno,这也印证了这个推测——%m的输出根本没用到当前的errno值。

二、如何准确判断非阻塞Connect的状态

不管errno是否靠谱,判断非阻塞连接的正确方式从来都不是直接看errno,而是结合socket的可写事件监听和getsockopt(SO_ERROR)来获取真实状态。具体步骤如下:

  1. 当connect返回-1时,先通过getsockopt(SO_ERROR)获取socket的真实错误码:

    • 如果错误码是EINPROGRESS,说明连接正在后台进行,需要继续等待;
    • 如果是其他错误码(比如ECONNREFUSED、ETIMEDOUT),说明连接直接失败。
  2. 监听socket的可写事件(用select/poll/epoll都可以):

    • 连接成功时,socket会变为可写;
    • 连接失败时,socket也会变为可写,但此时SO_ERROR会存储对应的错误码。
  3. 事件触发后,再次调用getsockopt(SO_ERROR)确认最终状态:

    • 返回0表示连接成功;
    • 返回非0值表示连接失败,该值就是具体的错误码。

修正后的示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <errno.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
    if (sock == -1) {
        perror("socket creation failed");
        return -1;
    }

    struct sockaddr_in remote;
    memset(&remote, 0, sizeof(remote));
    remote.sin_family = AF_INET;
    remote.sin_port = htons(80);
    // 替换为你要连接的IP
    if (inet_pton(AF_INET, "192.168.1.1", &remote.sin_addr) <= 0) {
        perror("invalid address");
        close(sock);
        return -1;
    }

    int connect_ret = connect(sock, (const struct sockaddr*)&remote, sizeof(remote));

    if (connect_ret == -1) {
        int err;
        socklen_t err_len = sizeof(err);
        
        // 先获取socket的真实错误状态
        if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len) == -1) {
            perror("getsockopt failed");
            close(sock);
            return -1;
        }

        printf("connect返回-1, errno=%d, SO_ERROR=%d\n", errno, err);
        printf("%m\n");

        if (err == EINPROGRESS) {
            printf("连接正在进行中,等待完成...\n");

            fd_set write_fds;
            struct timeval tv = {5, 0}; // 设置5秒超时
            FD_ZERO(&write_fds);
            FD_SET(sock, &write_fds);

            int select_ret = select(sock + 1, NULL, &write_fds, NULL, &tv);
            if (select_ret == -1) {
                perror("select failed");
                close(sock);
                return -1;
            } else if (select_ret == 0) {
                printf("连接超时\n");
                close(sock);
                return -1;
            } else {
                // 再次检查最终状态
                getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &err_len);
                if (err == 0) {
                    printf("连接成功建立!\n");
                } else {
                    printf("连接失败,错误码:%d (%s)\n", err, strerror(err));
                }
            }
        } else {
            printf("连接直接失败,错误码:%d (%s)\n", err, strerror(err));
        }
    } else {
        printf("连接立即成功!\n");
    }

    close(sock);
    return 0;
}

总结

  • 你遇到的errno为0的异常是老uclibc的实现bug,不用纠结,直接用SO_ERROR获取真实错误状态即可;
  • 非阻塞connect的正确判断逻辑永远是:监听可写事件 + 用getsockopt(SO_ERROR)确认最终状态,不要依赖errno的返回值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 08:37:31