【问题标题】:how do I find in C that a port is free to use?如何在 C 中找到可以免费使用的端口?
【发布时间】:2012-05-04 21:20:34
【问题描述】:

操作系统是 Linux。 我有一个可以实时更改其端口的服务器进程。不过我想提前知道绑定前端口是否空闲。

场景:服务器绑定 localhost:5000 并在 localhost:6000 接收到绑定请求。服务器必须检查端口是否空闲。此问题寻求提供检查端口是否空闲的例程的答案。

为了记录,我正在使用代码 sn-p 编辑我的问题,该代码检查端口是否可以免费使用。这并不意味着它会被使用。下面的代码回答了“如果端口现在可用”的问题,它不使用它。打开一个套接字,检查 bind 是否返回 EADDRINUSE 并关闭套接字。

#include <iostream>
#include <sys/socket.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <errno.h>

int main(int argc, char **argv) {

    struct sockaddr_in serv_addr;
    if( argc < 2 )
        return 0;
    int port = atoi(argv[1]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if( sockfd < 0 ) {
        printf("socket error\n");
        return 0;
    } else {
        printf("Opened fd %d\n", sockfd);
    }

     bzero((char *) &serv_addr, sizeof(serv_addr));
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(port);
     if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {

        if( errno == EADDRINUSE )
        {
            printf("the port is not available. already to other process\n");
        } else {
            printf("could not bind to process (%d) %s\n", errno, strerror(errno));
        }
    }

    if (close (sockfd) < 0 ) {
        printf("did not close fd: %s\n", strerror(errno));
        return errno;
    }

    return 0;


}

这是一些示例运行(部分输出)

[bash{1051}{51}]:[~/some_sources/checkbind]::./a.out 41067
the port is not available. already to other process
[bash{1052}{52}]:[~/some_sources/checkbind]::./a.out 22
could not bind to process (13) Permission denied
[bash{1053}{53}]:[~/some_sources/checkbind]::./a.out 22000
Opened fd 3

【问题讨论】:

  • 尝试绑定它,如果失败,它不是免费的...
  • Nicks 建议可能是最便携的解决方案,但也可能有特定于操作系统的方法来做到这一点。您将使用什么操作系统?
  • @Nick 看起来如果端口不是空闲的,则 bind 返回 EADDRINUSE (errno)。
  • 即使您设法验证该端口是空闲的,也不能保证在您实际尝试绑定它时它是空闲的。
  • 相关:unix.stackexchange.com/a/15513/31228,建议保留一系列端口的策略。

标签: c linux sockets network-programming


【解决方案1】:

这是一个明显的race condition,因为您系统上的其他进程可能会并行绑定到端口。因此,您找到的任何解决方案都将是不完美的,您仍然需要根据“尝试bind(),如果失败,请选择一个新的端口号并重试”的方法来编写它。

【讨论】:

  • 我同意可能会出现竞争条件,但在我的系统中这种情况是不可能的。 “选择下一个端口”的方法也是不可接受的。
  • @cateof: 什么是不可能的?竞争条件是不可能的,还是不能尝试绑定然后看看是否失败?
  • 如果您至少有两个进程竞争资源,则可能出现竞争情况。在我的系统中,只有一个进程尝试绑定。
  • @cateof 如果只是进程尝试绑定,那不就意味着其他端口一定是空闲的吗?
  • @cateof 也许两个进程同时启动,想想sshd 在您的程序启动时被重新启动。如果您无法控制所涉及的所有参数,请始终假设适用竞争条件。所以只是尝试绑定,看看是否成功。这是迄今为止最好和最安全的方式。
【解决方案2】:

我自己也为此苦苦挣扎,并稍微修改了您的代码。

解决方法是设置serv_addr.sin_port = 0(自动分配端口)。

注意bind()getsockname() 行中有从sockaddr_insockaddr 的不干净转换。我已经在很多地方看到过它,我正在寻找更安全的解决方案。

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

// ... snip ...

int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0) {
    printf("socket error\n");
    return;
}
printf("Opened %d\n", sock);

struct sockaddr_in serv_addr;
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = 0;
if (bind(sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
    if(errno == EADDRINUSE) {
        printf("the port is not available. already to other process\n");
        return;
    } else {
        printf("could not bind to process (%d) %s\n", errno, strerror(errno));
        return;
    }
}

socklen_t len = sizeof(serv_addr);
if (getsockname(sock, (struct sockaddr *)&serv_addr, &len) == -1) {
    perror("getsockname");
    return;
}

printf("port number %d\n", ntohs(serv_addr.sin_port));


if (close (sock) < 0 ) {
    printf("did not close: %s\n", strerror(errno));
    return;
}

程序输出

Opened 4
port number 59081

【讨论】:

  • "在getsockname(...) 行中有一个从sockaddr_insockaddr 的危险转换。"你认为它在什么方面是危险的?也许您想使用sockaddr_storage 以确保真正安全。
  • 它们的大小相同,均为 16 字节。 sin_lensin_family 在两个结构中位于相同的位置。我只是不喜欢 reinterpret_cast 演员表。
【解决方案3】:

如果您的服务器被告知要使用哪个端口,只需bind() 即可。认真的。

当然,您可以解析/proc/net/tcp 并查看端口是否在使用中。但是然后呢?你仍然需要打电话给bind(),因为你知道你的端口是免费的,它会告诉你端口是否是空闲的,所以通过/proc/net/tcp 并做所有这些都是没有意义的(慢! ) 字符串生成和解析以及额外的内核通过不是非常优化的诊断路径(阅读:与bind() 相比超级慢)诊断路径,只是为了在您完成解析之前获取可能已经过时的信息。所以只要打电话给bind() 就开心了。

【讨论】:

    猜你喜欢
    • 2010-09-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-22
    • 1970-01-01
    • 2011-02-10
    • 2017-01-29
    • 1970-01-01
    相关资源
    最近更新 更多