多个客户端如何同时连接同一个服务器的同一个端口

注意
本文最后更新于 2024-01-08,文中内容可能已过时。

多个客户端如何同时连接同一个服务器的同一个端口

首先介绍两个原则:

  • socket 的主键,即如何确定唯一的一个 socket:一个 socket 由{SRC-IP, SRC-PORT, DEST-IP, DEST-PORT, PROTOCOL}来唯一确定,注意,协议也非常重要,比如 TCP 和 UDP 协议。

  • 操作系统进程或者 socket 映射:一个进程可以和多个 socket 关联(可以打开 socket 或者监听 socket)

现在我们来进行根据例子来学习这两个规则。

举个例子,两个客户端连接到同一个服务器端口,比如 socket1 {SRC-A, 100, DEST-X,80, TCP} and socket2{SRC-B, 100, DEST-X,80, TCP},意思是主机 A 连接到服务器 X 的 80 端口,主机 B 也连接到服务器 X 的 80 端口,现在服务器如何处理这两个 socket 取决于这个服务器是单线程的还是多线程的(这个后面会解释),重点是,一个服务器可以同时连接多个 socket。

跟协议有没有状态无关,两个客户端可以连接到同一个服务器端口是因为我们可以为每一个客户端分配一个不一样的 socket(不同客户端,IP 绝对不同),相同的客户端也可以由多个 socket 连接到同一个服务器端口,因为这些 socket 使用不同的端口。

那服务器如何知道要响应哪一个 socket 呢?对于监听着这个端口的服务器进程来说,可能会有多个 socket(来自于同一个客户端或者不同的客户端),只要服务器知道哪一个请求对应着哪一个 socket,他就能找到正确的 socket 然后响应对应的客户端。

服务器上的两个不同的进程可以监听同一个端口吗?因为协议也是 socket 定义的一部分,因此,这是可能的,比如:两个客户端,一个使用 UDP 一个使用 TCP 连接到同一个服务器上的同一个端口,然后两个服务器进程同时监听这个端口,(也必须有两个进程监听这个端口),一个处理 TCP 请求一个处理 UDP 请求。

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

当我提到 A 和 B 这两个进程的概念时,请考虑它们之间并没有父子关系。OS(特别是 UNIX) 的设计允许子进程从父进程继承所有文件描述符 (File Descriptor FD)。因此,进程 A 监听的所有 socket,可以被更多 A 的子进程 A1、A2、..监听。但是一个独立的进程 B(即与 A 没有父子关系) 不能监听同一个 socket。另外,还要注意,在一个操作系统 (或它的网络库) 上不允许两个独立的进程监听同一个 socket,这是一个规则,到目前为止,大多数操作系统都遵守了这个规则。然而,人们可以创建自己的操作系统来违反这一限制。


这里的 socket 的含义 对应着《网络是怎样连接的》学习笔记中的 中文 套接字 三个字的含义。

一个 socket 对应一个 TCP 连接。

正常情况下我们访问服务器,比如通过浏览器,或者在 Java 里指定 URL 来访问服务器,都不会指定端口,实际上同一台客户端上并发地访问服务器的时候,就会使用随机端口来建立 TCP 连接,Chrome 最多允许对同一个 Host 建立六个 TCP 连接。也就是 6 个 socket。在客户端发起请求的时候,比如在 Java 里通过代码访问服务器,TCP 连接还没建立,所以发起 HTTP 请求的时候,客户端是不知道自己将要使用哪个端口的。

在用 Netty 创建客户端的时候,我们是可以指定端口的,而在 JavaScript 中用 Ajax 发起对服务端的请求的时候,是无法指定的,也就是说用 Netty 发起请求,真的可以很精确

关于 Netty,请看《Netty 设计》小节


0%