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

458

我了解端口的基本原理。但是,我不明白的是如何让多个客户端同时连接到例如80端口。我知道每个客户端都有一个唯一的(对于他们的机器来说)端口。服务器是否从可用端口回复给客户端,并简单地说明回复来自80?这是如何工作的?


请查看此链接:https://dev59.com/ZnA65IYBdhLWcg3wxRs8 - Ashkan Kh. Nazary
5个回答

530
首先,“端口”只是一个数字。连接到端口实际上表示的是在其“目标端口”头字段中指定该数字的包。
现在,对于有状态协议和无状态协议,您的问题有两个答案。
对于无状态协议(即UDP),没有问题,因为“连接”不存在——多个人可以向同一端口发送数据包,并且它们的数据包会以任何顺序到达。没有人处于“连接”状态。
对于有状态协议(如TCP),连接由源IP地址、目标IP地址和源和目标端口组成的四元组标识。因此,如果两个不同的机器连接到第三台机器上的同一端口,则存在两个不同的连接,因为源IP不同。如果相同的机器(或两个共享相同IP地址的NAT或其他方式)从单个远程端点连接两次,则连接通过源端口区分(通常是随机的高位端口)。
简单地说,如果我从我的客户端两次连接到同一Web服务器,则这两个连接将从我的角度具有不同的源端口,而从Web服务器的角度具有不同的目标端口。因此,即使这两个连接具有相同的源和目标IP地址,也没有歧义。
端口是一种将IP地址进行复用的方法,以便不同的应用程序可以侦听相同的IP地址/协议对。除非应用程序定义自己的高级协议,否则无法复用端口。如果同时使用相同的协议进行两个连接具有相同的源和目标IP地址以及相同的源和目标端口,则它们必须是同一个连接。

17
如果您从客户端两次连接到同一Web服务器,那么这两个连接的目标端口也将相同。只有源端口是不同的。 - notacat
3
“并在远程端口上设定目的地端口。”从服务器的角度来看,连接具有不同的源端口。澄清了。 - Borealid
94
如果两个使用相同协议的连接具有相同的源 IP、目标 IP和相同的源端口、目标端口,它们必须是同一连接。- 这应该被加入维基百科! - Ritesh
10
在NAT场景中有两个“源端口”在发挥作用。一个是原始计算机设置的源端口,另一个是路由器外部的源端口。后者由路由器选择,而不是由主机选择。由于每个主机在内部具有不同的IP地址,因此不会发生冲突。 - Borealid
2
如果两个使用相同协议的并发连接具有相同的源IP和目标IP以及相同的源端口和目标端口,则它们必须是同一连接。我认为只有在说出“并发”时,这个陈述才是正确的。客户端选择的临时端口可能稍后会被重用,用于与相同的ip:port识别的同一服务器建立后续连接,从而到达相同的4元组,但这将是两个不同的连接,在时间上是不同的。实际上,我正在尝试从数据包跟踪中重构TCP连接,因此遇到了这个问题。 - Janus Varmarken
显示剩余14条评论

473

重要提示:

很抱歉,“Borealid”的回答不够精确且有些错误——首先,回答这个问题与有状态或无状态无关,最重要的是对套接字元组的定义是不正确的。

首先请记住以下两条规则:

  1. 套接字的主键:一个套接字由 {源IP、源端口、目标IP、目标端口、协议} 来确定,而不是由 {源IP、源端口、目标IP、目标端口} 来确定——协议是套接字定义的重要部分。

  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)所断言的相同主题。但是,这是错误的答案,因为“协议”也是套接字定义的一部分。因此,如果两个进程在同一节点上使用不同的协议,则可以侦听相同的端口。例如,两个无关的客户端(一个使用TCP,另一个使用UDP)可以连接并与同一服务器节点和同一端口通信,但必须由两个不同的服务器进程提供服务。

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

当服务器进程正在监听端口时,意味着多个套接字可以同时连接并与同一个服务器进程通信。如果服务器仅使用单个子进程为所有套接字提供服务,则称该服务器为单进程/线程服务器;如果服务器使用多个子进程为每个套接字提供服务,则称该服务器为多进程/线程服务器。请注意,无论服务器类型如何,服务器始终可以/应该使用相同的初始套接字进行回应(无需分配另一个服务器端口)。

如果可以的话,建议阅读相关书籍和其他两卷。

关于父子进程的说明(回应“Ioan Alexandru Cucu”的问题/评论)

当我提到与两个进程A和B相关的任何概念时,请注意它们之间没有父子关系。操作系统(尤其是UNIX)通过设计允许子进程从父进程继承所有文件描述符(FD)。因此,进程A正在侦听的所有套接字(在类UNIX操作系统中也是FD的一部分)可以由许多其他进程A1、A2等依赖于与A的父子关系进行侦听。但是独立的进程B(即与A没有父子关系的进程)无法侦听同一套接字。此外,请注意,不允许两个独立的进程侦听同一套接字的规则存在于操作系统(或其网络库)中,并且迄今为止,大多数操作系统都遵守此规则。然而,人们可以创建自己的操作系统,这些操作系统可以很好地违反此限制。


5
不确定"因此,如果两个进程在同一节点上使用不同的协议,则只能同时侦听同一个端口"是否真实可靠...您可以让一个进程侦听某个端口,然后对其进行分叉。这将导致两个进程侦听同一个端口。当新连接到达时,操作系统将负责决定哪个进程处理请求。 - Ioan Alexandru Cucu
2
@Ioan Alexandru Cucu - 你说得对,为了解决像你这样的问题,我已经在我的回答中添加了一条注释。感谢你提出这个问题。但是,请注意,操作系统不会从已经在套接字上侦听的进程中分叉(至少我不知道),而是应用程序可能会分叉。在这种情况下,程序必须小心地通过父进程和/或子进程侦听和处理传入的数据。 - KGhatak
3
值得补充的是,即使进程A不是子进程,如果进程A通过本地Unix域套接字将套接字文件描述符作为附属消息(也称控制消息)使用sendmsg()系统调用和SCM_RIGHTS传递给进程B,则独立的进程B仍然有机制接管套接字。这不仅适用于套接字,而且适用于任何由进程拥有的文件描述符可以被传输到另一个进程。 - Tuxdude
2
很棒的解释。谢谢。 - DrunkenMaster
2
很好的解释,伙计! - atiqkhaled
显示剩余15条评论

242

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.

这很容易理解,因为从客户端的角度来看,他拥有一个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

查看实际发生的情况

首先,让我们使用netstat查看此计算机上正在发生的情况。我们将使用端口500而不是80(因为在端口80上发生了大量的事情,但从功能上讲并没有什么区别)。

    netstat -atnp | grep -i ":500 "

正如预期的那样,输出为空。现在让我们启动一个Web服务器:

    sudo python3 -m http.server 500

现在,再次运行netstat的输出如下:
    Proto Recv-Q Send-Q Local Address           Foreign Address         State  
    tcp        0      0 0.0.0.0:500             0.0.0.0:*               LISTEN      - 

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

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

    quicknet -m tcp -t localhost:500 -p Test payload.

这是一个简单的脚本(https://github.com/grokit/dcore/tree/master/apps/quicknet),它打开一个TCP套接字,发送有效载荷(在这种情况下为“测试有效载荷。”),等待几秒钟并断开连接。在此过程中再次执行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 -

如果您与另一个客户端连接并再次执行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 -

换句话说,客户端使用了另一个随机端口进行连接。因此,IP地址之间永远不会混淆。


39
这应该是最佳答案。 - cha
该页面 https://github.com/grokit/quickweb 显示404。 - Alexandre Santos
1
@AlexandreSantos https://github.com/grokit/dcore/tree/master/apps/quicknet - N0thing
127.0.0.1是一个地址,而不是一个端口。 - user207421
你说“5元组:(本地IP,本地端口,远程IP,远程端口,协议)”,那么,在一个端口上使用不同的协议启动两个服务器是否可能?例如一个TCP和一个HTTP?如果不行,为什么? - voltento
显示剩余6条评论

30

通常情况下,对于每个连接的客户端,服务器会派生出一个子进程与客户端进行通信(TCP)。父服务器将已建立的套接字切换给子进程,以便与客户端进行通信。

当您从子服务器向套接字发送数据时,操作系统中的TCP协议栈会创建一个数据包并将“源端口”设置为80,然后将其返回给客户端。


2
所以如果一个服务器有1000个并发连接(我知道这很高),它将不得不处理1000个线程!?那似乎不受控制。或者使用纤程(线程捆绑)。 - IamIC
5
并非所有的web服务器都是多线程(如带有worker模块的Apache)或多进程(如带有pre-fork模块的Apache)。可以寻找一些非线程化的能力很强的web服务器,如Lighty(以前叫做Lighttpd)和NginX。即使在多线程环境下,你也不必立刻处理所有传入的连接。你可以使用一个带有预设最大大小的队列。 - Ashkan Kh. Nazary
因此,既然发送回客户端的数据包据说来自端口80,那么这是否意味着当数据经过主服务器时,它可以再次定向到正确的子进程? - Griffin
因此,由于返回给客户端的数据包中的头部被称为来自端口80,这是否意味着客户端程序将不断地寻找来自该端口的响应? - Griffin
@m1tk4,因为客户端使用HTTP/1.1管道技术,即在同一套接字上进行多个“GET”请求,所以响应实际上来自80号端口。因此,即使HTTP是无状态的,客户端-服务器套接字/TCP也不是无状态的,响应必须来自同一子进程。 - d1val
在20世纪90年代,派生一个新进程的日子已经过去了。现在它是一个线程、完成端口或只是非阻塞select()循环处理的另一件事情。 - user207421

7
服务器上的同一端口(例如80)可以连接多个客户端,因为在服务器端,在创建套接字并绑定(设置本地IP和端口)之后,对该套接字调用listen会告诉操作系统接受传入连接。当客户端尝试在端口80上连接到服务器时,服务器套接字将调用accept。这将为试图连接的客户端创建一个新的套接字,并且使用相同的端口80会为后续客户端创建新的套接字。斜体字表示系统调用。Ref http://www.scs.stanford.edu/07wi-cs244b/refs/net2.pdf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接