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

非阻塞式端口连接检测函数异常问题排查求助

问题

需要实现一个可移植、非阻塞的函数检测服务器指定端口是否监听。当前实现中,即使远程服务已停止,FD_ISSET(sock, &write_set)仍返回true,导致误判服务正常。

当前实现代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>

int check_connection(const char* ip_address, int port) {
    struct sockaddr_in server_addr;
    int sock, flags;

    // Create a socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        return -1;
    }

    // Set server address parameters
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);

    // Convert IP address to binary form and set it in the server address structure
    if (inet_pton(AF_INET, ip_address, &server_addr.sin_addr) <= 0) {
        perror("Invalid IP address");
        close(sock);
        return -1;
    }

    // Set socket to non-blocking mode
    if ((flags = fcntl(sock, F_GETFL, 0)) < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("Failed to set socket to non-blocking mode");
        close(sock);
        return -1;
    }

    // Connect to the server
    int result = connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

    printf("connect() -> res: %d\n", result);

    if (result < 0 && errno != EINPROGRESS) {
        perror("Connection failed");
        close(sock);
        return -1;
    }

    // Check if the connection is established
    fd_set write_set;
    FD_ZERO(&write_set);
    FD_SET(sock, &write_set);
    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    result = select(sock + 1, NULL, &write_set, NULL, &timeout);
    
    printf("select() -> res: %d\n", result);

    if (result <= 0 || !FD_ISSET(sock, &write_set)) {
        perror("Connection timed out or failed");
        close(sock);
        return -1;
    }

    // Connection successful
    close(sock);
    return 0;
}

int main(int argc, char *argv[]) {
    if(argc == 3) {
        if(check_connection(argv[1], atoi(argv[2])) == 0) {
            printf("Service is up\n");
        } else {
            printf("Service is down\n");
        }
    } else {
        printf("usage:\n");
        
        printf("  %s <ip> <port>\n", argv[0]);
    }
}

测试结果

服务正常时:

$ ./main 10.0.1.100 80
connect() -> res: -1
select() -> res: 1
Service is up

$ nc -vz 10.0.1.100 80
Connection to 10.0.1.100 80 port [tcp/http] succeeded!

服务停止时(端口无监听):

$ ./main 10.0.1.100 443
connect() -> res: -1
select() -> res: 1
Service is up

$ nc -vz 10.0.1.100 443
nc: connect to 10.0.1.100 port 443 (tcp) failed: Connection refused

问题原因与解决方法

核心问题

非阻塞socket的select返回写就绪,仅表示连接操作已完成,不代表连接成功。当远程端口无监听时,TCP会收到RST包,此时socket会变为写就绪状态,但实际连接已失败。必须通过getsockopt获取socket的错误状态才能判断连接是否成功。

解决步骤

在select返回成功且FD_ISSET为true后,添加以下步骤:

  1. 调用getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len)获取socket的错误码
  2. 若错误码为0,则连接成功;否则连接失败

修正后的完整代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <poll.h>

int check_connection(const char* ip_address, int port) {
    struct sockaddr_in server_addr;
    int sock, flags;

    // Create a socket
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation failed");
        return -1;
    }

    // Set server address parameters
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);

    // Convert IP address to binary form and set it in the server address structure
    if (inet_pton(AF_INET, ip_address, &server_addr.sin_addr) <= 0) {
        perror("Invalid IP address");
        close(sock);
        return -1;
    }

    // Set socket to non-blocking mode
    if ((flags = fcntl(sock, F_GETFL, 0)) < 0 || fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) {
        perror("Failed to set socket to non-blocking mode");
        close(sock);
        return -1;
    }

    // Connect to the server
    int result = connect(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

    printf("connect() -> res: %d\n", result);

    if (result < 0 && errno != EINPROGRESS) {
        perror("Connection failed");
        close(sock);
        return -1;
    }

    // Check if the connection is established
    fd_set write_set;
    FD_ZERO(&write_set);
    FD_SET(sock, &write_set);
    struct timeval timeout;
    timeout.tv_sec = 5;
    timeout.tv_usec = 0;

    result = select(sock + 1, NULL, &write_set, NULL, &timeout);
    
    printf("select() -> res: %d\n", result);

    if (result <= 0 || !FD_ISSET(sock, &write_set)) {
        perror("Connection timed out or failed");
        close(sock);
        return -1;
    }

    // 关键:检查socket实际错误状态
    int error = 0;
    socklen_t len = sizeof(error);
    if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
        perror("getsockopt failed");
        close(sock);
        return -1;
    }
    if (error != 0) {
        errno = error;
        perror("Connection failed");
        close(sock);
        return -1;
    }

    // Connection successful
    close(sock);
    return 0;
}

int main(int argc, char *argv[]) {
    if(argc == 3) {
        if(check_connection(argv[1], atoi(argv[2])) == 0) {
            printf("Service is up\n");
        } else {
            printf("Service is down\n");
        }
    } else {
        printf("usage:\n");
        
        printf("  %s <ip> <port>\n", argv[0]);
    }
}

测试验证

服务停止时,修正后的代码会输出:

$ ./main 10.0.1.100 443
connect() -> res: -1
select() -> res: 1
Connection failed: Connection refused
Service is down

与nc的检测结果一致。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 18:47:14