TCP:两个不同的套接字可以共享一个端口吗?

K J*_*K J 97 sockets port network-programming tcp

这可能是一个非常基本的问题,但它让我感到困惑.

两个不同的连接插座可以共用一个端口吗?我正在编写一个应该能够处理超过10万个并发连接的应用服务器,而且我们知道系统上可用的端口数量大约为60k(16位).连接的套接字被分配给新的(专用)端口,因此这意味着并发连接的数量受端口数量的限制,除非多个套接字可以共享同一个端口.所以问题.

我在这里先向您的帮助表示感谢!

Rem*_*eau 136

一个服务器套接字监听在一个端口上.该服务器上所有已建立的客户端连接都与该连接的服务器端上的相同侦听端口相关联.建立的连接由客户端和服务器端IP /端口对的组合唯一标识.只要与不同的客户端 IP /端口对关联,同一服务器上的多个连接可以共享相同的服务器 IP /端口对,并且服务器将能够处理与可用系统资源允许的客户端数量相同的客户端至.

客户端,新的出站连接通常使用随机客户端端口,在这种情况下,如果您在很短的时间内建立了大量连接,则可能会耗尽可用端口.

  • @ user2268997:您不能使用单个套接字连接到多个服务器.您必须为每个连接创建一个单独的套接字. (5认同)
  • @Remy Connections 不仅通过源/目标端口/IP 来区分,还通过协议(TCP、UDP 等)来区分,如果我没记错的话。 (4认同)
  • 谢谢你的回答,雷米!你的回答就是我好奇的一切。;) (3认同)
  • @OndraPeterka:是的,但并非所有平台都对此进行限制。例如,Windows 愉快地允许单独的 IPv4 和 IPv6 服务器套接字在同一个本地 IP:Port 上侦听而无需跳过箍,但 *Nix 系统(包括 Linux 和 Android)不允许。 (2认同)
  • @RemyLebeau 客户端和服务器端套接字之间有区别吗?我问这个是因为我想使用单个 Socket 连接到多个服务器。AFIK 在建立连接后它们之间没有区别。但我找不到使用单个套接字**启动**到多个主机、端口对的连接的 API。 (2认同)
  • @FernandoGonzalezSanchez:一个客户端可以有多个TCP套接字绑定到同一本地IP /端口对,只要它们连接到不同的远程IP /端口对即可。这并非特定于Windows,这是TCP总体工作方式的一部分。 (2认同)
  • @FernandoGonzalezSanchez:尽管 [MSDN 文档](https://msdn.microsoft.com/en-us/library/windows/desktop/ms740621.aspx) 说,如果两个 TCP 侦听套接字尝试绑定到相同的 IP/端口(通配符或其他)在两个套接字上都启用了“SO_REUSEADDR”,第二个“bind()”仍然失败并显示“WSAEADDRINUSE”。我刚试过。让两个 TCP 服务器侦听同一个 IP/端口对是没有意义的,甚至文档也警告不要这样做。 (2认同)
  • “*要求我们推荐或查找书籍、工具、软件库、教程或其他场外资源的问题与 Stack Overflow 无关,因为它们往往会吸引固执的答案和垃圾邮件。*” (2认同)

N0t*_*ing 131

端口上的TCP/HTTP侦听:许多用户如何共享同一端口

那么,当服务器侦听TCP端口上的传入连接时会发生什么?例如,假设您在端口80上有一个Web服务器.假设您的计算机的公共IP地址为24.14.181.229,并且尝试连接到您的人的IP地址为10.1.2.3.此人可以通过打开到24.14.181.229:80的TCP套接字连接到您.很简单.

直觉(和错误地),大多数人认为它看起来像这样:

    Local Computer    | Remote Computer
    --------------------------------
    <local_ip>:80     | <foreign_ip>:80

    ^^ not actually what happens, but this is the conceptual model a lot of people have in mind.
Run Code Online (Sandbox Code Playgroud)

这很直观,因为从客户端的角度来看,他有一个IP地址,并连接到IP:PORT的服务器.由于客户端连接到端口80,那么他的端口也必须是80?这是一个明智的想法,但实际上不会发生什么.如果这是正确的,我们只能为每个外部IP地址服务一个用户.一旦远程计算机连接,他就会将端口80占用端口80连接,而其他任何人都无法连接.

必须要理解三件事:

1.)在服务器上,进程正在侦听端口.一旦获得连接,它就会将其交给另一个线程.通信永远不会占用监听端口.

2.)操作系统通过以下5元组唯一标识连接:(本地IP,本地端口,远程IP,远程端口,协议).如果元组中的任何元素不同,那么这是一个完全独立的连接.

3.)当客户端连接到服务器时,它会选择一个随机的,未使用的高阶源端口.这样,单个客户端可以为同一目标端口提供最多约64k的服务器连接.

因此,这实际上是客户端连接到服务器时创建的内容:

    Local Computer   | Remote Computer           | Role
    -----------------------------------------------------------
    0.0.0.0:80       | <none>                    | LISTENING
    127.0.0.1:80     | 10.1.2.3:<random_port>    | ESTABLISHED
Run Code Online (Sandbox Code Playgroud)

看看实际发生了什么

首先,让我们使用netstat来查看这台计算机上发生了什么.我们将使用端口500而不是80(因为端口80上发生了大量的东西,因为它是一个公共端口,但在功能上它并没有什么区别).

    netstat -atnp | grep -i ":500 "
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,输出是空白的.现在让我们开始一个Web服务器:

    sudo python3 -m http.server 500
Run Code Online (Sandbox Code Playgroud)

现在,这是再次运行netstat的输出:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      - 
Run Code Online (Sandbox Code Playgroud)

所以现在有一个进程在端口500上主动侦听(状态:LISTEN).本地地址是0.0.0.0,这是"侦听所有IP地址"的代码.一个容易犯的错误是只侦听端口127.0.0.1,它只接受来自当前计算机的连接.所以这不是连接,这只是意味着请求绑定()到端口IP的进程,并且该进程负责处理到该端口的所有连接.这暗示了每个计算机只能在一个端口上监听一个进程的限制(有多种方法可以使用多路复用来解决这个问题,但这是一个更复杂的主题).如果Web服务器正在侦听端口80,则它无法与其他Web服务器共享该端口.

现在,让我们将用户连接到我们的机器:

    quicknet -m tcp -t localhost:500 -p Test payload.
Run Code Online (Sandbox Code Playgroud)

这是一个简单的脚本(https://github.com/grokit/quickweb),它打开一个TCP套接字,发送有效负载(在这种情况下为"Test payload."),等待几秒钟并断开连接.发生这种情况时再次执行netstat会显示以下内容:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:54240      ESTABLISHED -
Run Code Online (Sandbox Code Playgroud)

如果您与另一个客户端连接并再次执行netstat,您将看到以下内容:

    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      -
    tcp        0      0 192.168.1.10:500        192.168.1.13:26813      ESTABLISHED -
Run Code Online (Sandbox Code Playgroud)

...也就是说,客户端使用另一个随机端口进行连接.因此,IP地址之间永远不会混淆.

  • 我喜欢读这个.非常翔实. (5认同)
  • 这是我在SO上见过的最好的答案。 (3认同)
  • @premktiw:是的,多个客户端套接字可以同时绑定到同一个本地IP /端口对,如果它们连接到不同的服务器IP /端口对,那么本地+远程对的元组是唯一的.是的,客户端可能总共拥有超过64K的并发连接.只要服务器IP /端口对是唯一的,就可以从单个端口连接到可能无限数量的服务器(受可用操作系统资源,可用路由器端口等限制). (3认同)
  • @N0thing“这样,单个客户端可以通过相同的目标端口与服务器建立最多约 64k 的连接。” 因此,在实践中,如果客户端没有同时两次或多次连接到相同的服务器和端口,那么客户端甚至可以拥有超过 64K 的连接。真的吗。如果是,则意味着从客户端的单个端口它可以连接到许多不同的服务器进程(例如套接字连接是不同的)。那么总而言之,多个客户端套接字可以驻留在客户端计算机上的同一端口上吗?请阅读我对“Remey Lebeau”答案的评论。感谢:D (2认同)
  • @bibstha 当所有传入连接都被拒绝时,防火墙如何处理随机端口? (2认同)
  • @PatrykG 随机端口用于客户端,防火墙不会查看连接客户端端口,而是查看目标端口。 (2认同)

Jer*_*ner 30

已连接的套接字分配给新的(专用)端口

这是一种常见的直觉,但这是不正确的.连接的套接字未分配给新的/专用端口.TCP堆栈必须满足的唯一实际约束是(local_address,local_port,remote_address,remote_port)的元组对于每个套接字连接必须是唯一的.因此,只要端口上的每个套接字连接到不同的远程位置,服务器就可以使用相同的本地端口的许多TCP套接字.

请参阅http://books.google.com/books?id=ptSC4LpwGA0C&lpg=PA52&dq=socket%20pair%20tuple&pg=PA52#v=onepage&q=socket%20pair%20tuple&f=false中的"套接字对"段落

  • @GMsoF:`bind()`可以在`connect()`之前在客户端使用. (4认同)
  • 你说的完全正确的服务器端.但是,BSD套接字API的结构意味着传出的客户端端口在实践中必须是唯一的,因为`bind()`操作在`connect()`操作之前,甚至是隐式的. (3认同)
  • 一个端口上只能有一个 TCP 套接字“侦听”,但可以有多个“连接”TCP 套接字同时使用该端口发送和接收数据。(这些将是侦听套接字上的“accept()”返回的套接字) (2认同)

pet*_*ica 7

从理论上讲,是的.练习,而不是.大多数内核(包括linux)都不允许您bind()使用已分配的端口.让这个允许的不是一个非常大的补丁.

在概念上,我们应该区分套接字端口.套接字是双向通信端点,即我们可以发送和接收字节的"事物".这是一个概念性的东西,在名为"socket"的包头中没有这样的字段.

端口是能够识别套接字的标识符.在TCP的情况下,端口是16位整数,但也有其他协议(例如,在unix套接字上,"端口"本质上是一个字符串).

主要问题如下:如果传入的数据包到达,内核可以通过其目标端口号识别其套接字.这是一种最常见的方式,但它不是唯一的可能性:

  • 套接字可以通过传入数据包的目标IP进行标识.例如,如果我们有一台同时使用两个IP的服务器就是这种情况.然后我们可以在相同的端口上运行不同的Web服务器,但是在不同的IP上运行.
  • 套接字也可以通过它们的端口和ip 来识别.许多负载平衡配置就是这种情况.

因为您正在使用应用程序服务器,所以它可以执行此操作.

  • ??? 我的答案有什么问题?我扩展一点。 (2认同)
  • 他没有问关于制作第二个`bind()`。 (2认同)