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

我了解端口工作的基本原理。然而,我不明白的是多个客户端如何同时连接到端口80。我知道每个客户端都有一个唯一的端口(用于他们的机器)。服务器是否从一个可用端口向客户端返回,并简单地声明该响应来自80?这是如何工作的呢?

376349 次浏览

通常,对于每个连接的客户端,服务器都派生出一个与客户端通信的子进程(TCP)。父服务器将已建立的套接字传递给子进程,该套接字与客户端进行通信。

当你将数据从子服务器发送到套接字时,操作系统中的TCP堆栈会创建一个返回客户端的数据包,并将“from port”设置为80。

首先,“端口”只是一个数字。所有“到端口的连接”实际上表示的是一个数据包,该数据包在其“目的端口”报头字段中指定了该数字。

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

对于无状态协议(即UDP),没有问题,因为“连接”不存在——多人可以向同一个端口发送数据包,他们的数据包将以任何顺序到达。没有人处于“连接”状态。

对于有状态协议(如TCP),连接由由源端口和目的端口以及源IP地址和目的IP地址组成的4元组标识。因此,如果两台不同的机器连接到第三台机器上的相同端口,则存在两个不同的连接,因为源ip不同。如果同一台机器(或两台经过NAT转换或共享相同IP地址的机器)两次连接到单个远程端,则根据源端口(通常是随机的高编号端口)来区分连接。

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

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

重要的是:

我很抱歉地说,来自“Borealid”的响应是不精确的,有点不正确——首先,回答这个问题与有状态或无状态没有关系,最重要的是,套接字的元组定义是不正确的。

首先记住以下两条规则:

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

  2. OS进程&套接字映射:一个进程可以与(可以打开/可以监听)多个套接字相关联,这对许多读者来说可能是显而易见的。

示例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:这是一个非常有趣的问题:“服务器上两个不同的进程可以侦听相同的端口吗?”如果你不认为协议是socket定义参数之一,那么答案是否定的。这是因为我们可以说,在这种情况下,试图连接到服务器端口的单个客户机将没有任何机制来提及客户机打算连接到两个侦听进程中的哪一个。这与规则(2)所断言的主题相同。然而,这是错误的答案,因为'protocol'也是套接字定义的一部分。因此,同一节点上的两个进程只有在使用不同协议的情况下才能侦听相同的端口。例如,两个不相关的客户端(比如一个使用TCP,另一个使用UDP)可以连接并通信到同一个服务器节点和相同的端口,但它们必须由两个不同的服务器-进程提供服务。

服务器类型-单机&多个:

当一个服务器的进程侦听一个端口时,这意味着多个套接字可以同时连接同一个服务器进程并与之通信。如果一个服务器只使用一个子进程来服务所有的套接字,那么这个服务器被称为单进程/线程,如果一个子进程使用许多子进程来服务每个套接字,那么这个服务器被称为多进程/线程服务器。注意,不管服务器的类型如何,服务器都可以/应该总是使用相同的初始套接字来响应(不需要分配另一个服务器端口)。

建议和其余两个卷如果可以。

关于父/子过程的说明(回复“Ioan Alexandru Cucu”的查询/评论)

当我提到与A和B这两个过程有关的概念时,请考虑它们不是由父子关系联系在一起的。操作系统(尤其是UNIX)在设计上允许子进程从父进程继承所有文件描述符(FD)。因此,进程a侦听的所有套接字(在UNIX中,如OS也是FD的一部分),都可以被更多进程A1、A2、..但一个独立进程B(即与A没有父子关系)不能侦听同一个套接字。此外,还要注意禁止两个独立进程侦听同一个套接字的规则是基于操作系统(或其网络库)的,到目前为止,大多数操作系统都遵守这一规则。然而,一个人可以创建自己的操作系统,这很可能违反这些限制。

TCP / HTTP端口监听:多个用户如何共享同一个端口

那么,当服务器侦听TCP端口上的传入连接时会发生什么呢?例如,假设您在端口80上有一个web服务器。让我们假设您的计算机具有24.14.181.229的公共IP地址,而试图连接您的人具有10.1.2.3的IP地址。此人可以通过打开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元组(local-IP, local-port, remote-IP, remote-port, protocol)唯一标识连接。如果元组中的任何元素是不同的,那么这是一个完全独立的连接。

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      -

所以现在有一个进程在端口500上积极监听(状态:LISTEN)。本地地址为0.0.0.0,这是“监听所有人”的代码。一个容易犯的错误是监听地址127.0.0.1,该地址只接受来自当前计算机的连接。因此,这不是一个连接,这只是意味着请求bind()到端口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地址之间永远不会混淆。

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

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

斜体字是系统调用。

裁判

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