【问题标题】:Choose a local source port that is already in use选择一个已经在使用的本地源端口
【发布时间】:2014-07-02 00:14:03
【问题描述】:

我想使用特定的本地端口连接到远程服务器,而不是内核随机分配的端口。我可以通过在调用connect() 到远程服务器之前调用bind() 绑定到本地端口来做到这一点。

我的问题是,如果我要使用的本地端口已被另一个应用程序使用,会发生什么情况?无论如何,我应该能够使用它,只要目标或远程端口不同(就像服务器可以accept() 端口 80 上的多个连接一样)。但是在这种情况下我的bind 调用不应该失败吗?如果是这样,我该如何设置套接字以使用另一个应用程序已经在使用的本地端口?

我想这样做的原因是我正在尝试编写一个本地代理来连接到检查源端口的服务器应用程序。如果源端口错误,服务器将不允许连接。客户端应用程序连接到我的代理,我希望我的代理使用相同的端口连接到服务器 - 但如果代理在同一台机器上,它将无法工作,因为该端口已被连接的应用程序使用到我的代理。

【问题讨论】:

  • 关于“我的bind调用会不会失败”的问题,你应该可以自己测试一下,反正你打算写这个程序。
  • 我真的很想知道我可以依赖什么行为及其背后的原因,我不是专家,不会相信单个测试用例 :)
  • 我也不是网络编程方面的专家。我提出这个建议是因为我会这样做来确定答案。 This answer 有点笼统,但它可能会帮助您朝着正确的方向前进。

标签: c sockets tcp network-programming posix


【解决方案1】:

您可以提出无论如何都应该能够使用它的论点,但是除非两个绑定不同,否则 TCP 实现不允许您这样做。例如,您可能能够绑定到具有不同 IP 地址的同一端口。

允许重叠绑定有两个问题:

  1. 如果两个应用程序都调用 accept 会发生什么?他们是否会争夺传入的连接?

  2. 如果两个应用程序都尝试与同一个 IP 和端口建立出站连接,会发生什么情况?如何区分这两个连接?

现在这些问题都可以解决了。但我不知道有任何麻烦的实现。争论是应用程序必须合作,否则它们会得到令人惊讶的结果。如果他们合作,他们可以共享绑定的套接字。

所以答案是:如果您不与拥有该端口的其他应用程序合作,那么您无权共享它。如果您正在与其他应用程序合作,请使用您的平台支持的方法要求它给您一份其套接字的副本。

【讨论】:

  • 谢谢。为什么内核会在您bind 时抛出错误,而不是在您尝试listen 时在情况1 和connect 时在情况2?。条件可以安全地是“您无法在该端口/IP 上侦听,因为其他人已经在侦听”和“您无法连接到该端口 IP,因为其他人从同一端口连接”,然后让 bind总是成功。
  • @Andrew 那么如果一个进程binds 和另一个connects 和一个传入连接从一个IP 和端口接收到另一个试图connect 的端口会发生什么?如果他们合作,他们不需要同时绑定。如果他们不是,那么他们不能同时绑定。解决所有这些问题要麻烦得多,而且无论如何流程都必须合作。如果他们合作,他们不需要bind——他们可以共享绑定的套接字。
  • 我猜你不能让connect 继续,如果其他人是listening,反之亦然,因为bind 不一定暗示listen。我明白你的意思,虽然它很乱。如果我无法控制的另一个应用程序正在使用某个端口,您是说我无法将该端口与不同的目标 IP 一起使用吗?我已经对我正在尝试做的问题进行了一些澄清。
  • @Andrew 没错,除了以特定于平台的方式侵入性地从应用程序中“窃取”套接字之外别无他法(例如,在 Linux 上,您可以从 /proc/<pid>/fd/ 中抢走它)。该应用程序不知道如何处理所有可能的问题案例,也不知道如何与您协调以避免它们。
【解决方案2】:

>但是在这种情况下我的绑定调用不应该失败吗,

如果套接字没有设置 SO_REUSEADDR 选项,则是。

> 如果是这样,我该如何设置套接字以使用已被另一个应用程序使用的本地端口?

您的应用程序和另一个应用程序都必须在要绑定到本地端口的套接字上设置 SO_REUSEADDR 选项。

以下代码从端口 1111 以命令行参数的形式连接到 HTTP 服务器:

#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netdb.h>

#define CLIENT_PORT 1111
#define SERVER_PORT 80

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

  struct sockaddr_in client_name, server_name;
  struct hostent *server_info;

  if (argc != 2)
    return printf("Exactly one argument is required: host to connect\n"), 1;

  int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (sock_fd < 0)
    return perror("socket"), 1;

  /* Without the next 4 lines, bind refuses to use the same port */
  int reuseaddr = 1;
  if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
                 sizeof(reuseaddr)) < 0)
    return perror("setsockopt"), 1;

  client_name.sin_family = AF_INET;
  client_name.sin_port = htons(CLIENT_PORT);
  client_name.sin_addr.s_addr = htonl(INADDR_ANY);

  if (bind(sock_fd, (struct sockaddr *) &client_name, 
           sizeof(struct sockaddr_in)) < 0)
    return perror("bind"), 1;

  server_name.sin_family = AF_INET;
  server_name.sin_port = htons(SERVER_PORT);
  if ((server_info = gethostbyname(argv[1])) == NULL)
    return printf("Unknown host: %s\n", argv[1]), 1;

  server_name.sin_addr = *(struct in_addr *) server_info->h_addr;

  if (connect(sock_fd, (struct sockaddr *) &server_name, 
              sizeof(server_name)) < 0)
    return perror("connect"), 1;

  return 0;
}

>如果我要使用的本地端口已被另一个应用程序使用会怎样?

如果没有 SO_REUSEADDR(尝试注释掉 setsockopt 周围的 4 行),bind 会失败:

$ ./client google.com
$ ./client stackoverflow.com
bind: Address already in use

使用 SO_REUSEADDR,您可以连接到不同的远程服务器:

$ ./client google.com
$ ./client stackoverflow.com

但是 connect 将不允许您打开两个具有相同源 目标的套接字:

$ ./client google.com
$ ./client google.com
connect: Cannot assign requested address

【讨论】:

    【解决方案3】:

    bind 只知道一个端点。

    假设两个套接字绑定到同一个端口。传入的数据包应该路由到哪一个?

    另一方面,accept 认识双方。

    【讨论】:

    • 如果两个socket都连接了,可以通过对端IP和端口来确定目的地。如果套接字已绑定但未连接,则不应接收任何数据包。
    猜你喜欢
    • 1970-01-01
    • 2015-10-22
    • 1970-01-01
    • 2018-02-11
    • 1970-01-01
    • 1970-01-01
    • 2020-09-16
    • 2012-01-02
    • 2013-11-09
    相关资源
    最近更新 更多