多个客户端如何同时连接到服务器上的一个端口,比如80?

Iam*_*mIC 380 connection port tcp client-server http

我了解端口如何工作的基础知识.但是,我没有得到的是多个客户端如何同时连接到端口80.我知道每个客户端都有一个唯一的(用于他们的机器)端口.服务器是否从可用端口回复客户端,并简单地说明回复来自80?这是如何运作的?

Bor*_*lid 446

首先,"端口"只是一个数字.所有"与端口的连接"实际上表示的是具有在其"目标端口"头字段中指定的该数字的分组.

现在,您的问题有两个答案,一个用于有状态协议,另一个用于无状态协议.

对于无状态协议(即UDP),没有问题,因为"连接"不存在 - 多个人可以将数据包发送到同一个端口,并且它们的数据包将以任何顺序到达.没有人处于"连通"状态.

对于有状态协议(如TCP),连接由4元组标识,包括源和目标端口以及源和目标IP地址.因此,如果两台不同的机器连接到第三台机器上的同一端口,则有两个不同的连接,因为源IP不同.如果同一台机器(或两台NAT后面或共享相同的IP地址)连接两次到一个远端,则连接由源端口(通常是随机的高编号端口)区分.

简单地说,如果我从客户端连接到同一个Web服务器两次,则两个连接将具有来自我的角度的不同源端口和来自Web服务器的目标端口.因此,即使两个连接具有相同的源和目标IP地址,也没有歧义.

端口是一种多路复用 IP地址的方法,以便不同的应用程序可以侦听相同的IP地址/协议对.除非应用程序定义自己的更高级别协议,否则无法复用端口.如果使用相同协议的两个连接同时具有相同的源和目标IP以及相同的源和目标端口,则它们必须是相同的连接.

  • "如果使用相同协议的两个连接具有相同的源和目标IP以及相同的源和目标端口,则它们必须是相同的连接." - 这应该在维基百科! (77认同)
  • 如果从客户端连接到同一Web服务器两次,则两个连接也将具有相同的目标端口.只有源端口不同. (9认同)
  • @HelloWorld NAT场景中有两个"源端口".原始计算机设置的源端口,以及路由器上的外部源端口.后者由路由器选择,而不是主机.由于内部每个主机具有不同的IP地址,因此不会发生冲突. (4认同)
  • @notacat:"和远程端的目标端口****".从服务器的角度来看,连接具有不同的源端口.澄清. (3认同)
  • "如果使用相同协议的两个**并发**连接具有相同的源和目标IP以及相同的源和目标端口,则它们必须是相同的连接." 我相信这句话只有在它说_concurrent_时才是真的.客户端选择的临时端口可以稍后重用,以便与同一个ip:port标识的同一服务器进行后续连接,从而到达相同的4元组,但这些将是两个不同时间点的两个不同连接.我实际上正面临这个问题,因为我正在尝试从数据包跟踪重建TCP连接. (2认同)

KGh*_*tak 352

重要:

我很遗憾地说"Borealid"的回答是不精确的,有些不正确 - 首先,回答这个问题与有状态或无状态无关,最重要的是套接字的元组定义是不正确的.

首先记住以下两条规则:

  1. 套接字的主键:套接字{SRC-IP, SRC-PORT, DEST-IP, DEST-PORT, PROTOCOL}不由{SRC-IP, SRC-PORT, DEST-IP, DEST-PORT}- 标识- 协议是套接字定义的重要部分.

  2. 操作系统进程和套接字映射:进程可以与多个套接字关联(可以打开/可以监听),这对许多读者来说可能是显而易见的.

示例1:连接到同一服务器端口的两个客户端意味着:socket1 {SRC-A, 100, DEST-X,80, TCP}socket2{SRC-B, 100, DEST-X,80, TCP}.这意味着主机A连接到服务器X的端口80,而另一个主机B也连接到同一服务器X到同一端口80.现在,服务器如何处理这两个套接字取决于服务器是单线程还是多线程(我会稍后解释一下).重要的是一台服务器可以同时收听多个套接字.

回答帖子的原始问题:

无论有状态还是无状态协议,两个客户端都可以连接到同一个服务器端口,因为对于每个客户端,我们可以分配不同的套接字(因为客户端IP肯定会有所不同).同一客户端也可以有两个连接到同一服务器端口的套接字 - 因为这样的套接字不同SRC-PORT.公平地说,"Borealid"基本上提到了相同的正确答案,但是对无状态/完全的提及有点不必要/令人困惑.

回答关于服务器如何知道要回答哪个套接字的问题的第二部分.首先要了解,对于正在侦听同一端口的单个服务器进程,可能有多个套接字(可能来自同一客户端或来自不同客户端).现在,只要服务器知道哪个请求与哪个套接字相关联,它就可以始终使用相同的套接字响应适当的客户端.因此,服务器永远不需要在其自己的节点中打开另一个端口,而不是客户端最初尝试绑定连接的原始端口.如果任何服务器在绑定套接字后分配不同的服务器端口,那么在我看来服务器正在浪费其资源,并且它必须要求客户端再次连接绑定到分配的新端口.

更完整一点:

示例2:这是一个非常有趣的问题,服务器的两个不同进程可以监听同一个端口.如果您不将协议视为参数定义套接字之一,则答案为否.主动这是因为我们可以说,在这种情况下,尝试连接到服务器端口的单个客户端将没有任何机制来提及客户端打算进行的两个侦听过程中的哪一个.这与规则(2)所声称的主题相同.然而,这是错误的答案,因为'protocol'也是套接字定义的一部分.因此,只有在使用不同协议时,同一节点中的两个进程才能侦听同一端口.例如,两个不相关的客户端(比如一个使用TCP而另一个使用UDP)可以绑定连接并与同一服务器节点和同一端口进行通信,但它们必须由两个不同的服务器进程提供服务.

服务器类型 - 单个和多个:

当服务器进程侦听端口意味着多个套接字可以同时连接并与同一服务器进程通信时.如果服务器只使用一个子进程来为所有套接字提供服务,则服务器称为单进程/线程,如果服务器使用许多子进程通过一个子进程为每个套接字服务,则服务器被称为多个进程/线程服务器.请注意,无论服务器的类型如何,服务器都可以/应该始终使用相同的初始套接字来响应(不需要分配另一个服务器端口).

如果可以,建议书籍和两卷的其余部分.

关于父母/子女过程的说明(回应'Ioan Alexandru Cucu'的查询/评论)

无论我在哪里提到与A和B两个过程有关的任何概念,都要考虑它们与父子关系无关.操作系统(尤其是UNIX)允许子进程从父进程继承所有文件描述符(FD).因此,进程A监听的所有套接字(在UNIX中都是OS的一部分也是FD的一部分)可以被更多进程A1,A2,...监听,只要它们通过父子关系与A相关联.独立进程B(即与A没有父子关系)不能听同一个套接字.此外,还要注意,禁止两个独立进程监听相同套接字的规则存在于操作系统(或其网络库)中,并且到目前为止大多数操作系统都遵守该规则.但是,可以创建自己的操作系统,这很可能违反这些限制.

  • 不确定"因此,同一节点中的两个进程只有在使用不同的协议时才能侦听同一个端口"才是真的......你可以让一个进程侦听一个端口,然后自行分叉.然后,您将最终在同一端口上侦听两个进程.当新连接到达时,操作系统有责任决定两个进程中的哪一个将处理请求. (4认同)
  • 很好的解释。另一件事是,使用“SO_REUSEADDR”两个进程可以共享相同的套接字,但这是多播的。如果我有新的 ServerSocket(80) 并且我为每个 Accept() 跨越新线程,那么我一次为一个客户端提供服务(即使使用非阻塞队列,我也无法同时发送数据包)。因此,单线程/多线程 Web 服务器之间的唯一真正区别是,在第一个客户端的 HTTP 请求完成之前,单个进程无法为第二个客户端提供服务。 (2认同)
  • @Ioan Alexandru Cucu - 你是对的,为了适应像你这样的问题,我在回复中添加了一个注释.谢谢你提出这个问题.但是,请注意,操作系统不会从已经在套接字上侦听的进程(至少我不知道)进行分叉,而是应用程序可能会分叉.在这种情况下,程序必须小心地通过父进程和/或子进程监听和处理传入数据. (2认同)
  • 值得补充的是,如果processA使用`sendmsg将套接字的文件描述符作为辅助消息(也称为控制消息)通过本地unix域套接字传递给processB,那么独立进程B仍然有一种机制从进程A接管套接字. ()`系统调用`SCM_RIGHTS`.这不仅适用于套接字,但进程拥有的任何文件描述符都可以转移到另一个进程,即使它不是子进程. (2认同)

N0t*_*ing 178

端口上的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,这是"全部监听"的代码.一个容易犯的错误是侦听地址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/dcore/tree/master/apps/quicknet),它打开一个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地址之间永远不会混淆.

  • 这应该是最重要的答案 (27认同)

m1t*_*tk4 26

通常,对于每个连接客户端,服务器都会分叉与客户端(TCP)通信的子进程.父服务器将子进程切换到已建立的套接字,该套接字与客户端进行通信.

当您从子服务器向套接字发送数据时,操作系统中的TCP堆栈会创建一个返回客户端的数据包,并将"从端口"设置为80.

  • @IanC并非所有的Web服务器都是多线程(带有工作模块的Apache)或多进程(带有预分叉模块的Apache).为一些非常强大的非线程Web服务器寻找Lighty(正式Lighttpd)和NginX.即使在多线程环境中,您也不必*一次处理所有传入连接.您可以使用具有预设最大大小的队列. (4认同)
  • 因此,如果服务器说有 1,000 个同时连接(我知道这很高),它就必须与 1,000 个线程抗衡!?这似乎失控了。或者是否使用了纤维(螺纹桶)。 (2认同)

pum*_*cat 5

多个客户端可以连接到服务器上的同一个端口(比如 80),因为在服务器端,在创建套接字绑定(设置本地 IP 和端口)之后,在套接字上调用侦听,告诉操作系统接受传入的连接。

当客户端尝试通过端口 80 连接到服务器时,会在服务器套接字上调用接受调用。这会为尝试连接的客户端创建一个新套接字,类似地,将为使用相同端口 80 的后续客户端创建新套接字。

斜体字是系统调用。

参考

http://www.scs.stanford.edu/07wi-cs244b/refs/net2.pdf