WebSocket 服务器如何处理多个传入的连接请求?

Posted

技术标签:

【中文标题】WebSocket 服务器如何处理多个传入的连接请求?【英文标题】:How WebSocket server handles multiple incoming connection requests? 【发布时间】:2015-04-15 12:28:34 【问题描述】:

根据here:

HTTP 升级标头请求服务器切换 从 HTTP 到 WebSocket 协议的应用层协议

客户端握手在 IE10 之间建立 HTTP-on-TCP 连接 和服务器。服务器返回 101 响应后, 应用层协议从 HTTP 切换到使用 WebSockets 之前建立的 TCP 连接。

HTTP 在这一点上完全不存在。使用 轻量级 WebSocket 有线协议,现在可以发送消息或 由任一端点随时接收。

所以,我的理解是,在第一个客户端完成与服务器的握手后,服务器的 80 端口将被 WebSocket 协议独占HTTP 不再在 80 端口上工作

那么第二个客户端如何与服务器交换握手。 毕竟 WebSocket 握手是 HTTP 格式。

添加 1

感谢到目前为止的所有答案。他们真的很有帮助。

现在我了解到,同一台服务器的 80 端口由多个 TCP 连接共享。这种共享是完全可以的,因为TCP 连接是由一个 5 元素元组标识的,正如Jan-Philip Gehrcke 指出的那样。

我想补充一些想法。

WebSocketHTTP 都只是应用层协议。 通常他们都依赖TCP 协议作为他们的传输。

为什么选择 80 端口?

WebSocket 设计有意选择服务器的 80 端口用于握手和后续通信。我认为设计者想让 WebSocket 通信看起来像正常的 HTTP 通信从传输层的角度来看(即服务器端口号仍然是 80) .但根据jfriend00 的回答,这个技巧并不总是能骗过网络基础设施。

协议从HTTPWebSocket是如何发生的?

来自 RFC 6455 - WebSocket 协议

基本上,它旨在尽可能接近将原始 TCP 暴露给 考虑到 Web 的限制,尽可能编写脚本。这也是 以这样一种方式设计,它的服务器可以与 HTTP 共享一个端口 服务器,通过使其握手成为有效的 HTTP 升级请求。一 可以在概念上使用其他协议来建立客户端-服务器 消息传递,但 WebSockets 的目的是提供一个相对 可以与 HTTP 共存并部署 HTTP 的简单协议 基础设施(例如代理),并且与 TCP 一样接近 鉴于安全考虑,可安全地与此类基础设施一起使用, 有针对性的添加以简化使用并保持简单的东西 简单(比如增加消息语义)。

所以我认为我对以下陈述是错误的:

握手请求 模仿 HTTP 请求,但通信 跟随不要。握手请求到达服务器的 80 端口。 因为是80端口,服务器会用HTTP协议处理。这就是为什么 WebSocket 握手请求必须是 HTTP 格式的原因。 如果是这样,我认为 HTTP 协议 必须 修改/扩展为 识别那些特定于 WebSocket 的东西。否则不会意识到 它应该屈服于 WebSocket 协议。

我觉得应该这样理解:

WebSocket 通信从来自的 有效 HTTP 请求开始 客户端到服务器。所以是遵循HTTP协议的服务器 解析握手请求并识别请求 协议变更。切换协议的是服务器。所以 HTTP 协议不需要更改。 HTTP 协议甚至不需要 了解 WebSocket。

WebSocket 和 Comet

因此,WebSocket 与Comet 技术的不同之处在于,WebSoket 不会将自身限制在当前的 HTTP 领域内以解决双向通信问题。

添加 2

一个相关问题:How does a browser establish connection with a web server on 80 port? Details?

【问题讨论】:

WebSocket 握手是“HTTP 格式”,但这并不意味着它使用 HTTP。 "The handshake resembles HTTP so that servers can handle HTTP connections as well as WebSocket connections on the same port. However the specific fields involved, and what follows after the handshake, do not conform to the HTTP protocol." 你认为同样的问题是如何解决的,忽略WebSockets?那就是:当一个在 80 端口上监听 HTTP 请求的服务器接收到多个传入的 HTTP 请求时会发生什么? @MattBall 我想我了解端口 80 上的多个请求。服务器只接受每个请求并使用单独的 TCP 连接为它们提供服务。在我最初的问题中,我的印象是首先是 HTTP 在端口 80 上工作,然后根据客户端的 UPGRADE 请求,HTTP 协议被 WebSocket 取代。因此,我的帖子中的问题。 【参考方案1】:

你的问题很好!

我想从涉及系统调用listen()accept()的角度来回答一下。我认为了解这两个调用的行为非常有见地,足以回答您的问题。

剧透:我们可以通过研究 TCP/IP 的工作原理来回答您的问题 :-)

对于问题的核心部分,取决于 HTTP 或 WebSocket 确实没有区别。共同点是 TCP over IP。发送 HTTP 请求需要在两方之间建立 TCP/IP 连接(我已尝试详细说明here)。

如果是简单的网络浏览器/网络服务器场景

    首先,在两者之间建立 TCP 连接(由客户端发起) 然后通过那个 TCP 连接(从客户端到服务器)发送一个HTTP 请求 然后通过相同的 TCP 连接(在另一个方向,从服务器到客户端)发送 HTTP 响应

在此交换之后,不再需要底层 TCP 连接,并且通常会被破坏/断开连接。如果出现所谓的“HTTP 升级请求”(可以认为是:“嘿,服务器!请将此升级为 WebSocket 连接!”),底层 TCP 连接继续存在,WebSocket 通信继续通过最初创建的相同 TCP 连接(上面的步骤 (1))。

这有望阐明 WebSocket 和 HTTP 之间的主要区别在于高级协议中的切换(从 HTTP 到 WebSocket)无需更改底层传输通道(TCP/IP 连接)。

通过同一个套接字处理多个 IP 连接尝试,如何处理?

这是一个我曾经与自己苦苦挣扎的话题,很多人不理解,因为它有点不直观。但是,当您了解操作系统提供的与套接字相关的基本系统调用如何工作时,这个概念实际上是非常简单的。

首先,需要了解 IP 连接是唯一五个信息定义的:

机器A的IP:PORT机器B的IP:PORT协议(TCP或UDP)

现在,socket 对象通常被认为代表一个连接。但这并不完全正确。它们可能代表不同的事物:它们可以是主动的或被动的。 passive/listen 模式下的套接字对象做了一些非常特别的事情,这对于回答您的问题很重要。

http://linux.die.net/man/2/listen 说:

listen() 将 sockfd 引用的套接字标记为被动套接字, 也就是说,作为一个套接字,将用于接受传入的连接 使用accept(2)请求。

也就是说,我们可以创建一个被动套接字来监听传入的连接请求。根据定义,这样的套接字永远不能代表一个连接。它只是监听连接请求。

让我们前往accept() (http://linux.die.net/man/2/accept):

accept() 系统调用与基于连接的套接字类型一起使用 (SOCK_STREAM,SOCK_SEQPACKET)。它提取第一个连接 请求侦听套接字的挂起连接队列, sockfd,创建一个新的连接套接字,并返回一个新文件 引用该套接字的描述符。新创建的套接字不是 处于聆听状态。原始套接字 sockfd 不受 这个电话。

让我们仔细消化一下,我认为现在这确实回答了您的问题。

accept() 不会改变之前创建的 passive 套接字的状态。它返回一个活动的(连接的)套接字(这样的套接字就代表了上面的五种信息状态——很简单,对吧?)。

通常,这个新创建的活动套接字对象然后被移交给另一个进程或线程,或者只是负责连接的“实体”。在accept() 返回这个连接的套接字对象后,accept() 可以在被动套接字上再次被调用,一次又一次——这就是所谓的接受循环 .

但是拨打accept() 需要时间,对吧?它不能错过传入的连接请求吗?刚刚引用的帮助文本中有更多基本信息:有一个待处理的连接请求队列!它由操作系统的 TCP/IP 堆栈自动处理。

这意味着虽然accept()只能一个接一个处理传入的连接请求,但即使以高速率或(准)传入的请求也不会丢失。同时地。可以说accept() 的行为限制了您的机器可以处理的传入连接请求的频率。然而,这是一个快速的系统调用,在实践中,首先会遇到其他限制——通常是那些与处理所有已接受的连接相关的限制到目前为止

【讨论】:

你的答案是迄今为止最好的,也很有启发性!这实际上是一个与 TCP 相关的问题,而不是与 HTTP/Websocket 相关的问题。在收到 HTTP UPGRADE 请求时执行的切换与 HTTP 和/或 Websockets 服务如何在同一个 TCP 端口上服务多个连接没有任何关系。 @Jan-PhilipGehrcke 我从您的帖子中学到了很多,非常感谢您的详细解释。但我只能标记一个答案。对此感到抱歉。改为加 1。 :) @Jan-PhilipGehrcke,今天我重新阅读了您的答案。我的印象是the server-client distinction only exists before the establishment of connection,一旦调用accept() 并授予另一个服务器端口,客户端和服务器只是equal peers 通过active TCP 连接相互交谈。 @Jan-PhilipGehrcke 在服务器端,端口切换发生从 80 到其他端口,比如 9999。所以服务器的 socketfd = (client IP, client port, server IP, 9999, tcp),但客户端的 socketfd = (client IP, client port, server IP, 80, tcp)。客户端如何以及何时知道端口变化? @smwikipedia 没有。没有端口开关。【参考方案2】:

您似乎在这里缺少的相对简单的事情是每个到服务器的连接(尤其是这里的 HTTP 服务器)创建它自己的套接字然后运行在那个插座上。在一个套接字上发生的事情完全独立在当前连接的任何其他套接字上发生的事情。因此,当一个套接字切换到 webSocket 协议时,不会改变其他当前或传入套接字连接的情况。这些人可以自行决定如何处理。

因此,打开的套接字可以使用 webSocket 协议,而其他传入连接可以是常规 HTTP 请求或创建新 webSocket 连接的请求。

所以,你可以有这种类型的序列:

    客户端 A 通过 HTTP 请求连接到端口 80 上的服务器以启动 webSocket 连接。此过程会在两者之间创建一个套接字。 服务器响应是,升级到 webSocket 请求,客户端和服务器都将仅用于此套接字的协议切换到 webSocket 协议。 客户端 A 和服务器开始使用 webSocket 协议交换数据包,并在接下来的几个小时内继续这样做。 客户端 B 通过常规 HTTP 请求连接到端口 80 上的同一服务器。此过程会在两者之间创建一个新套接字。 服务器看到传入的请求是正常的 HTTP 请求并发送响应。 当客户端 B 收到响应时,套接字关闭。 客户端 C 通过 HTTP 请求连接到端口 80 上的同一台服务器以升级到 webSocket。 服务器响应是,升级到 webSocket 请求,客户端和服务器都将仅用于此套接字的协议切换到 webSocket 协议。 此时,有两个使用 webSocket 协议的打开套接字可以进行通信,并且服务器仍在接受新连接,这些连接可以是常规 HTTP 请求,也可以是升级到 webSocket 协议的请求。

因此,服务器始终在端口 80 上接受新连接,这些新连接可以是常规 HTTP 请求,也可以是升级到 webSocket 协议的 HTTP 请求(从而启动 webSocket联系)。而且,在这一切进行的同时,已经建立的 webSocket 连接正在使用 webSocket 协议通过自己的套接字进行通信。

webSocket 连接和通信方案经过精心设计,具有以下特性:

    不需要新端口。传入端口(最常见的端口 80)可用于常规 HTTP 请求和 webSocket 通信。 因为不需要新端口,所以“通常”不需要更改防火墙或其他网络基础设施。事实证明,情况并非总是如此,因为可能必须修改一些期望 HTTP 流量的代理或缓存以处理(或避免)webSocket 协议流量。 同一个服务器进程可以轻松处理 HTTP 请求和 webSocket 请求。 可以在设置 webSocket 连接期间使用 HTTP cookie 和/或其他基于 HTTP 的身份验证方式。

回答您的其他问题:

1) 为什么选择 80 作为默认端口?设计师是否想制作 WebSocket 通信看起来像正常的 HTTP 通信 运输层面的观点? (即服务器端口是旧的 80)。

是的,请参阅上面的第 1-4 点。 webSockets 可以在现有的 HTTP 通道上建立,因此它们通常不需要更改网络基础设施。我要补充一点,不需要新的服务器或服务器进程,因为现有的 HTTP 服务器可以简单地添加 webSocket 支持。

2) 我试图描绘协议转换是如何在服务器上发生的。一世 图片有不同的软件模块用于处理 HTTP 或 WebSocket 流量。第一个服务器使用 HTTP 模块来处理 正常的 HTTP 请求。当它发现一个升级请求时,它会切换 使用 WebSocket 模块。

不同的服务器架构会以不同的方式处理 webSocket 数据包和 HTTP 请求之间的划分。在某些情况下,webSocket 连接甚至可能被转发到新进程。在其他情况下,它可能只是同一进程中的不同事件处理程序,它为套接字上的传入数据包流量注册,现在已切换到 webSocket 协议。这完全取决于 Web 服务器架构以及它如何选择处理 webSocket 流量。实现 webSocket 应用程序服务器端的开发人员很可能会选择与其特定 Web 服务器架构兼容的现有 webSocket 实现,然后编写在该框架内工作的代码。

就我而言,我选择了与 node.js(这是我的服务器架构)一起使用的 socket.io 库。该库为我提供了一个对象,该对象支持用于新连接 webSockets 的事件,然后是一组用于读取传入消息或发送传出消息的其他事件。初始 webSocket 连接的细节都由库处理,我不必担心这些。如果我想在建立连接之前要求身份验证,socket.io 库可以让我插入。然后我可以从任何客户端接收消息,向任何单个客户端发送消息或向所有客户端广播信息。我主要将它用于广播以在网页中“实时”保存一些信息,以便网页显示始终是最新的。每当服务器上的值发生变化时,我都会将新值广播给所有连接的客户端。

【讨论】:

我认为主动和被动套接字之间的区别对于回答这个问题非常重要。我喜欢你的回答,但你没有解释“所以,在任何时候,服务器仍在接受新连接”实际上是如何可能的。我希望在我的回答中提供了这条信息。 你怎么会认为我这么认为? "没有这样的socket状态。":有这样的状态!这是您应该再次阅读的 TCP/IP 基础知识的一部分。套接字可以是“被动的”(即侦听传入连接,而不代表连接)或“主动”(代表连接)。有时称为“主动开放”与“被动开放”。那是事实。以及我回答的重点。与 CPU 和带宽无关,我不提这些话题。 您可能想再次查看您的书籍。或者使用您选择的编程语言来创建基本 IP 连接。服务器端:首先创建一个套接字,将其置于侦听模式(使其被动!),然后开始接受传入连接。最初创建的套接字不会因此而改变,它仍然是被动。全部包含在我的答案中,请阅读。 “你所说的任何被动套接字似乎都对 OP 没有帮助”? OP的问题是“WebSocket服务器如何处理多个传入的连接请求?”。他指定:“服务器的80端口将被WebSocket协议独占,HTTP不再在80端口工作。”被动套接字(或称其为监听/接受套接字)的概念正在解开他的谜团。完全。【参考方案3】:

这是一个相当简单的概念,所以让我试着用简单的术语来描述它,以防其他迷失的灵魂想要掌握它而不必阅读所有那些冗长的解释。

多个连接

    网络服务器开始监听连接。发生这种情况:

    Web 服务器的主进程在端口80 上打开状态为listen 的被动套接字,例如9.9.9.9:809.9.9.9 是服务器 IP,80 是端口)。

    浏览器向服务器上的端口80 发出请求。发生这种情况:

    操作系统(简称 OS)在客户端分配一个随机出站端口,例如:1.1.1.1:67471.1.1.1 是客户端 IP,6747 是随机端口)。

    操作系统发送一个数据包,源地址为1.1.1.1:6747,目的地址为9.9.9.9:80。它经过各种路由器和交换机,到达目的服务器。

    服务器收到数据包。发生这种情况:

    服务器操作系统看到数据包的目标地址是它自己的 IP 地址之一,并根据目标端口将其传递给与端口 80 关联的应用程序。

    Web 服务器的主进程接受创建新活动套接字的连接。然后它通常会派生一个新的子进程,该子进程接管活动的套接字。被动套接字保持打开状态以接受新的传入连接。

现在从服务器发送到客户端的每个数据包都将具有这些地址:

来源:9.9.9.9:1553;目的地:1.1.1.1:80

并且从客户端发送到服务器的每个数据包都将具有这些地址:

来源:1.1.1.1:80;目的地:9.9.9.9:1553

HTTP -> WebSocket 握手

HTTP 是基于文本的协议。有关可用命令的列表,请参阅HTTP wiki。浏览器发送这些命令之一,Web 服务器做出相应的响应。

WebSocket 不基于 HTTP。它是一种二进制协议,可以同时在两个方向上发送多个消息流(全双工模式)。因此,如果不引入新的 HTTP 标准(例如HTTP/2),就无法直接建立 WebSocket 连接。但这只有在以下情况下才有可能:

    支持 WebSocket HTTP verbs/requests

    有一个不同于 80 的新专用端口用于特定于 WebSocket 的通信。

第一个不在协议范围内,第二个会破坏现有的网络基础设施。因为客户端/浏览器可以与同一个服务器建立多个 HTTP 连接,所以将其中一些从 HTTP 切换到 WebSocket 是两全其美的 - 保持相同的端口80,但允许使用与 HTTP 不同的协议。切换通过客户端发起的protocol handshake进行。

【讨论】:

我使用 Wireshark 分析了浏览器和服务器之间的 TCP 数据包流。 在服务器端没有选择随机出站端口(即您示例中的 1553 端口)。只有客户端的随机端口。服务器始终使用它侦听的端口(在我的场景中为 8080)。所以我不同意你的观察 3。 这个答案不正确。服务器不会为传入连接分配新端口。它使用与监听端口相同的端口。 很抱歉给您带来了困惑。刚刚更正了答案。我错误地将端口与套接字相关联。服务器需要为每个连接打开一个新的套接字,而不是一个端口,并且每个新套接字中的目标端口保持不变。 您的第 1 点包含一个不合逻辑:子进程的数量与是否存在 LISTEN 状态的端口无关。在第 2 点中,您完全忽略了 HTTP keepalive 作为默认设置。确切地说,元组描述了连接,而不仅仅是套接字,内核需要整个元组,而不仅仅是它的客户端部分。这里有太多错误,太多不相关的华夫饼无法回答问题。 @EJP,删除了没有回答问题的华夫饼。现在它很简短。还有其他错误吗?感谢您的反馈。我不打算给出详细的答案,因为这需要一本书的几页。但我同意跳过华夫饼可能比提供误导性信息更好。【参考方案4】:

回答您的问题:同时处理到端口 80 的 Websocket 和 HTTP 连接...

与端口 80 的并发 HTTP 连接的处理方式完全相同!

这意味着:在满意的 TCP 握手后,侦听 serviceip:80 的服务继续生成一个新进程或线程,并将该连接的所有通信移交给它(或通过执行与该事件关联的回调来服务请求正如异步nodejs所做的那样,正如jfriend00正确指出的那样)。

然后等待或处理队列中的下一个传入请求。

如果您想知道 HTTP 1.1 和 UPGRADE 请求在这一切上发挥了什么作用,上面的 MSDN article 非常清楚:

WebSocket 协议有两个部分:握手以建立 升级后的连接,然后是实际的数据传输。首先,一个客户 使用“升级:websocket”请求一个 websocket 连接,并且 “连接:升级”标头,以及一些特定于协议的 标头来建立正在使用的版本并设置握手。 服务器,如果它支持该协议,则以相同的方式回复 “升级:websocket”和“连接:升级”标头并完成 握手。一旦握手成功完成,数据 转移开始。

只有 Websocket 服务通常不会内置到 Web 服务器中,因此并不打算在端口 80 中侦听,由于 Web 服务器的透明转发,只能通过它访问。 Apache Web 服务器使用mod_proxy_wstunnel 来实现这一点。

当然,您也可以拥有一个带有内置 Web 套接字实现的 Web 服务器:例如,Apache Tomcat。

这里的主要内容是:Websocket 协议不是 HTTP。它有不同的用途。它是一个独立的应用层通信协议,也是建立在 TCP 之上的(虽然 TCP 不是必需的,但是符合 Websockets 应用层协议要求的传输层协议)。

Websocket 服务是与 Web 服务器服务一起运行的并行服务。

它使用现代 Web 浏览器支持的 Websocket 协议,实现接口的客户端部分。

您设置或构建 Websocket 服务,以便在 Websocket 客户端(通常是 Web 浏览器)和该服务之间建立持久的非 HTTP 连接。

主要优点是:Websocket 服务可以在需要时向客户端发送消息(“你们中的一个哥们已连接!”“您的团队刚刚进球!”),而不必等待客户端的显式请求进行更新。

您可以使用 HTTP 1.1 建立持久连接,但 HTTP 仅用于提供一组资源UPON REQUEST,然后关闭连接。

直到最近,在所有主要浏览器都支持 Websockets 之前,您只有两种选择来实现 Web 应用程序的实时更新:

实现 AJAX 长轮询请求,这是一个痛苦且低效的过程。

使用/构建浏览器插件(例如 Java 小程序支持插件)以便能够与您的更新服务建立非 HTTP 连接,这比长轮询更有效但更痛苦。

就服务的共享监听端口(可以是任何 TCP 端口,甚至不必对 Internet 开放,因为大多数 Web 服务器都支持 Web 套接字连接的透明转发)而言,它的工作方式与任何其他 TCP 服务:该服务只是监听它,当 TCP 握手结束时,存在一个 TCP 套接字供服务与客户端通信。

像往常一样,当分配一个唯一的 client_ip:client_TCP_port 对时,与侦听特定 TCP 套接字 (server_ip:service_TCP_port) 的服务的所有连接将被区分,client_TCP_port 由客户端在其可用 TCP 端口中随机选择。

如果您仍然对 Websocket 连接握手时发生的 HTTP->Websocket 应用程序协议切换以及它与底层 TCP 连接没有任何关系有疑问,我向您推荐Jan-Philip Gehrcke's answer,这是非常明确一个有启发性的内容,并且可能是您真正在寻找的内容。

【讨论】:

但是根据en.wikipedia.org/wiki/WebSocket:另外,通信是通过TCP端口号80完成的,这对于那些阻止非Web的环境来说是有好处的使用防火墙的 Internet 连接。 是的,他们是。但是只有 Web 服务器在监听 80 端口。在最常见的情况下,任何 Web 套接字通信都由 Web 服务器转发到 Web 套接字服务,并且 Web 套接字服务可以监听任何端口。另一种方法是在端口 80 上为 Web 套接字服务使用不同的公共 IP,但这会产生各种麻烦,而且几乎不会经常出现。 如果随机选择端口,通信如何通过防火墙?或者通信还是通过 TCP 80 端口,WebSocket 端口封装在里面。监听80端口的web服务器会检查封装的端口,转发给WebSocket服务? 为客户端上的每个连接选择随机 TCP 端口,而不是服务器。 (我为失误道歉)。无论如何,通常 web 服务器会将 websocket 连接转发到 websocket 服务的侦听地址 (ip:port),因此 websocket 服务将位于防火墙后面,侦听服务管理员决定用于其部署的任何端口,不一定是端口 80。 第一段中关于“生成新线程或进程”的部分只是处理传入套接字的一种方式,不是必需的,也不是所有服务器都这样做的方式。例如,node.js 不会那样做。

以上是关于WebSocket 服务器如何处理多个传入的连接请求?的主要内容,如果未能解决你的问题,请参考以下文章

如何处理共享同一通道的多个 goroutine

R如何处理关闭数据库连接

如何处理 Azure App Service WebSockets 超时?

GreenPlum 如何处理多个大型连接和同时工作负载?

Nginx如何处理一个连接

Node js - 如何处理多个异步任务