非阻塞式端口连接检测函数异常问题排查求助
问题
需要实现一个可移植、非阻塞的函数检测服务器指定端口是否监听。当前实现中,即使远程服务已停止,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后,添加以下步骤:
- 调用
getsockopt(sock, SOL_SOCKET, SO_ERROR, &error, &len)获取socket的错误码 - 若错误码为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
相关产品推荐
相关产品推荐

