我了解端口的基本原理。但是,我不明白的是如何让多个客户端同时连接到例如80端口。我知道每个客户端都有一个唯一的(对于他们的机器来说)端口。服务器是否从可用端口回复给客户端,并简单地说明回复来自80?这是如何工作的?
我了解端口的基本原理。但是,我不明白的是如何让多个客户端同时连接到例如80端口。我知道每个客户端都有一个唯一的(对于他们的机器来说)端口。服务器是否从可用端口回复给客户端,并简单地说明回复来自80?这是如何工作的?
重要提示:
很抱歉,“Borealid”的回答不够精确且有些错误——首先,回答这个问题与有状态或无状态无关,最重要的是对套接字元组的定义是不正确的。
首先请记住以下两条规则:
套接字的主键:一个套接字由 {源IP、源端口、目标IP、目标端口、协议}
来确定,而不是由 {源IP、源端口、目标IP、目标端口}
来确定——协议是套接字定义的重要部分。
操作系统进程和套接字映射:一个进程可以与(可以打开/可以监听)多个套接字相关联,这可能对许多读者来说是显而易见的。
例如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没有父子关系的进程)无法侦听同一套接字。此外,请注意,不允许两个独立的进程侦听同一套接字的规则存在于操作系统(或其网络库)中,并且迄今为止,大多数操作系统都遵守此规则。然而,人们可以创建自己的操作系统,这些操作系统可以很好地违反此限制。
sendmsg()
系统调用和SCM_RIGHTS
传递给进程B,则独立的进程B仍然有机制接管套接字。这不仅适用于套接字,而且适用于任何由进程拥有的文件描述符可以被传输到另一个进程。 - Tuxdude当服务器在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.
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
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:500 0.0.0.0:* LISTEN -
现在,让我们将用户连接到我们的机器:
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地址之间永远不会混淆。
通常情况下,对于每个连接的客户端,服务器会派生出一个子进程与客户端进行通信(TCP)。父服务器将已建立的套接字切换给子进程,以便与客户端进行通信。
当您从子服务器向套接字发送数据时,操作系统中的TCP协议栈会创建一个数据包并将“源端口”设置为80,然后将其返回给客户端。
select()
循环处理的另一件事情。 - user207421