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

94
根据 此处:
HTTP Upgrade头请求服务器将应用层协议从HTTP切换到WebSocket协议
客户端握手在IE10和服务器之间建立了一个HTTP-on-TCP连接。服务器返回其101响应后,应用层协议从HTTP切换到使用先前建立的TCP连接的WebSockets。
此时HTTP完全不再起作用。使用轻量级WebSocket线路协议,任何一端点现在都可以随时发送或接收消息。
所以,我的理解是,在第一个客户端完成与服务器的握手后,服务器的80端口将被WebSocket协议垄断。而HTTP不再在80端口上工作
那么第二个客户端如何与服务器交换握手呢?毕竟WebSocket握手是以HTTP格式进行的。

ADD 1

感谢迄今为止提供的所有答案。它们非常有帮助。

现在我明白了,同一服务器的80端口由多个TCP连接共享。这种共享完全没有问题,因为如Jan-Philip Gehrcke所指出的那样,TCP连接由5元组标识。

我想补充一些想法。

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

为什么选择80端口?

WebSocket的设计有意选择服务器的80端口进行握手和随后的通信。我认为设计者希望WebSocket通信从传输层的角度看起来像普通的HTTP通信(即服务器端口号仍为80)。但根据jfriend00的回答,这个技巧并不总是能愚弄网络基础设施。

协议是如何从HTTP转换到WebSocket的?

来自RFC 6455 - WebSocket协议

基本上,它旨在尽可能接近于仅向脚本公开原始TCP,考虑到Web的限制。 它还以这样的方式设计,使得其服务器可以与HTTP服务器共享端口,通过其握手成为有效的HTTP升级请求。 概念上,可以使用其他协议来建立客户端-服务器消息传递,但WebSocket的目的是提供一个相对简单的协议,可与HTTP和已部署的HTTP基础设施(例如代理)共存,并且尽可能接近TCP,考虑到安全性问题,在目标添加方面以简化使用并使简单的事情保持简单(例如添加消息语义)。

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

握手请求模仿HTTP请求,但随后的通信不同。握手请求到达服务器的80端口。因为它是80端口,服务器会将其视为HTTP协议。这就是为什么WebSocket握手请求必须采用HTTP格式的原因。如果是这样,我认为HTTP协议必须被修改/扩展以识别那些特定于WebSocket的事物。否则,它将无法意识到应该让步于WebSocket协议。
我认为应该这样理解:WebSocket通信始于客户端向服务器发送有效的HTTP请求。因此,服务器遵循HTTP协议解析握手请求并确定协议更改的开始。服务器切换协议。因此,HTTP协议不需要更改。HTTP协议甚至不需要了解WebSocket。
WebSocket和Comet

WebSocket与Comet技术不同的地方在于,WebSocket不仅局限于当前的HTTP领域来解决双向通信问题。

ADD 2

一个相关的问题:浏览器如何与80端口上的Web服务器建立连接?细节?


WebSocket握手是“以HTTP格式”进行的,但这并不意味着它使用HTTP。握手类似于HTTP,因此服务器可以在同一端口上处理HTTP连接和WebSocket连接。但是,涉及的特定字段以及握手后跟随的内容并不符合HTTP协议。 - Matt Ball
你认为如何解决这个问题,不考虑WebSockets?也就是说:当服务器在端口80上监听HTTP请求并接收到多个传入的HTTP请求时会发生什么? - Matt Ball
@MattBall 我认为我明白了如何在端口80上处理多个请求。服务器只是接受它们并使用单独的TCP连接来服务它们。在我的原始问题中,我只是有一个印象,即首先是HTTP在端口80上工作,然后根据客户端的UPGRADE请求,HTTP协议被WebSocket替换。因此,在我的帖子中提出了这个问题。 - smwikipedia
4个回答

117

你的问题很好!

我想从涉及系统调用 listen()accept() 的角度来尝试回答它。理解这两个调用的行为我认为是非常有洞察力和足够回答你的问题的。

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

对于问题的核心部分,我们甚至不需要区分“正常”的HTTP和WebSockets。共同点是 TCP over IP

发送HTTP请求需要在两个方之间建立一个已经建立的TCP/IP连接(我在这里稍微详细地阐述了一下)。

在简单的Web浏览器/ Web服务器场景中:

  1. 首先,双方之间建立一个TCP连接(由客户端发起)
  2. 然后通过该TCP连接发送HTTP请求(从客户端到服务器)
  3. 然后通过相同的TCP连接发送HTTP响应(从服务器到客户端)

在这个交换之后,底层的TCP连接通常不再需要并且会被销毁/断开。在所谓的“HTTP升级请求”(可以理解为:“嘿,服务器!请将此升级为WebSocket连接!”)的情况下,底层的TCP连接仍然存在,并且WebSocket通信通过最初创建的相同TCP连接进行(如上面的步骤(1))。

这希望澄清WebSocket和HTTP之间的关键区别是高级协议的切换(从HTTP转向WebSocket),而不改变底层传输通道(TCP/IP连接)。

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

这是我曾经自己苦苦挣扎过的一个话题,许多人不理解,因为它有点不直观。然而,当人们了解操作系统提供的基本套接字相关系统调用的工作原理时,这个概念实际上是相当简单的。

首先,需要欣赏一个IP连接由五个信息唯一定义:

机器A的IP:端口机器B的IP:端口以及协议(TCP或UDP)

现在,socket对象通常被认为是表示连接的对象。但这并不完全正确。它们可以代表不同的事物:它们可以是活动的或被动的。在被动/监听模式下的socket对象做着非常特殊的事情,这对于回答你的问题很重要。

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

listen()将sockfd所指向的套接字标记为被动套接字,也就是说, 该套接字将用于使用accept(2)来接受传入的连接请求。

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

让我们转到accept() (http://linux.die.net/man/2/accept):

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

让我们仔细理解一下,我认为这现在真正回答了你的问题。

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

通常,这个新创建的活动套接字对象随后会被移交给另一个处理连接的进程、线程或实体。在accept()返回这个连接的套接字对象之后,可以在被动套接字上再次调用accept(),一遍又一遍地调用——这就是所谓的接受循环

但调用accept()是需要时间的,对吗?它会错过传入的连接请求吗?在刚引用的帮助文本中有更重要的信息:有一个未决连接请求的队列!它会被您操作系统的TCP/IP堆栈自动处理。

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


5
你的答案是迄今为止最好的,也非常有指导意义!这实际上更多是一个与TCP相关的问题,而不是与HTTP/Websocket相关的问题。在接收到HTTP UPGRADE请求时执行的交接并不涉及HTTP和/或Websockets服务如何在同一TCP端口上处理多个连接。 - NotGaeL
1
@Jan-PhilipGehrcke,我从您的帖子中学到了很多,并非常感谢您详细的解释。但是我只能标记一个答案。对此我很抱歉。我会加1分作为补偿。 :) - smwikipedia
@Jan-PhilipGehrcke,今天我重新阅读了你的回答。我有一个印象,即“服务器-客户端区分仅存在于连接建立之前”,一旦调用accept()并授予另一个服务器端口,客户端和服务器只是通过“活动”TCP连接相互交谈的“平等对等体”。 - smwikipedia
@Jan-PhilipGehrcke 在服务器端,端口从80切换到其他端口,比如9999。因此,服务器的socketfd =(客户端IP,客户端端口,服务器IP,9999,tcp),但客户端的socketfd =(客户端IP,客户端端口,服务器IP,80,tcp)。客户端何时以及如何知道端口更改? - smwikipedia
@Jan-PhilipGehrcke 噢,我的错!! accept() 系统调用只是创建一个 活动 套接字,服务器端口80没有改变。所以客户端不需要更改任何端口。套接字对象确实意味着不仅仅是 IP 地址和端口,它们代表了 被动/主动 - smwikipedia
2
@smwikipedia 不,没有端口切换。 - user207421

26
相对简单的一点是,每个到服务器的连接(特别是到您的HTTP服务器)都会创建自己的套接字,然后在该套接字上运行。在一个套接字上发生的事情与当前连接的任何其他套接字上发生的事情完全独立。因此,当一个套接字切换到webSocket协议时,这不会改变当前或即将连接的任何其他套接字连接正在发生的情况。它们可以自行决定如何处理其连接。
所以,打开的套接字可以使用webSocket协议,而其他传入的连接可以是常规的HTTP请求或请求创建新的webSocket连接。
您可以有这种类型的序列:
1. 客户端A使用HTTP请求在端口80上连接到服务器以启动webSocket连接。这个过程在两者之间创建了一个套接字。 2. 服务器回应是的,升级为webSocket请求,并且客户端和服务器只在此套接字上切换协议到webSocket协议。 3. 客户端A和服务器开始使用WebSocket协议交换数据包,并继续进行几个小时。 4. 客户端B使用常规的HTTP请求在同一服务器的端口80上连接。这个过程在两者之间创建了一个新的套接字。 5. 服务器看到传入的请求是正常的HTTP请求,并发送回响应。 6. 当客户端B接收到响应时,套接字关闭。 7. 客户端C使用HTTP请求在同一服务器的端口80上连接以升级为webSocket。 8. 服务器回应是的,升级为webSocket请求,并且客户端和服务器只在此套接字上切换协议到webSocket协议。 9. 在这一点上,有两个正在使用WebSocket协议通信的打开套接字,并且服务器仍在接受可以是常规HTTP请求或可以请求升级为webSocket协议的新连接。因此,服务器始终在端口80上接受新连接,这些新连接可以是常规的HTTP请求,也可以是请求升级为webSocket协议(从而启动webSocket连接)的HTTP请求。而与此同时,已经建立的webSocket连接正在使用webSocket协议通过它们自己的套接字进行通信。
webSocket连接和通信方案被精心设计,以具有以下特征:
1. 不需要新端口。传入端口(通常是端口80)可用于处理常规HTTP请求和webSocket通信。 2. 由于不需要新端口,因此“通常”不需要更改防火墙或其他网络基础设施。但实际情况并非总是如此,因为一些代理或缓存可能需要修改以处理(或避免)webSocket协议流量。 3. 同一服务器进程可以轻松处理HTTP请求和webSocket请求。 4. 可以在设置webSocket连接期间使用HTTP cookie和/或其他基于HTTP的身份验证手段。
回答您的其他问题:
1)为什么选择80作为默认端口? 设计者是否希望从传输层的角度使webSocket通信看起来像正常的HTTP通信?(即服务器端口是老式的80)
是的,请参见我上面立即给出的4个要点。 webSockets可以在现有的HTTP通道上建立,因此通常不需要进行任何网络基础设施更改。 我还要补充的是,不需要新的服务器或服务器进程,因为现有的HTTP服务器可以简单地添加webSocket支持。
2)我试图想象在服务器上如何进行协议转换。我想象中有处理HTTP或Websocket流量的不同软件模块。首先,服务器使用HTTP模块处理常规HTTP请求。当它发现Upgrade请求时,它将切换到使用WebSocket模块。
不同的服务器架构会以不同的方式处理webSocket数据包和HTTP请求之间的分离。在某些情况下,甚至可能将webSocket连接转发到新的进程中。在其他情况下,可能只是同一进程中不同的事件处理程序已经在套接字上注册了用于传入数据包流量的websocket协议。这完全取决于Web服务器的架构以及它选择如何处理WebSocket流量。实现WebSocket应用程序的服务器端的开发人员很可能会选择与其特定Web服务器架构兼容的现有WebSocket实现,然后编写适合该框架的代码。
在我的情况下,我选择了与node.js(我的服务器架构)一起使用的socket.io库。该库为我提供了一个支持新连接的WebSocket事件和一组用于读取传入消息或发送传出消息的其他事件的对象。初始WebSocket连接的详细信息由库处理,我无需担心任何问题。如果我想要在建立连接之前要求进行身份验证,则socket.io库有一种方法可让我插入。然后,我可以从任何客户端接收消息,向任何单个客户端发送消息或向所有客户端广播信息。我主要将其用于广播以使网页上的信息“实时”,以便网页显示始终是最新的。每当服务器上的值更改时,我都会将新值广播到所有连接的客户端。

1
我认为区分主动套接字和被动套接字对于回答这个问题非常重要。我喜欢你的回答,但你没有解释“因此,服务器始终在接受新连接”是如何实现的。我希望在我的回答中提供了这一信息。 - Dr. Jan-Philip Gehrcke
没有这种套接字状态。请重新阅读有关TCP/IP基础知识的内容。套接字可以是“被动”的(即正在侦听传入连接,不代表连接),也可以是“主动”的(表示连接)。有时称为“主动打开”与“被动打开”。这是事实。这是我的回答的重点。与CPU和带宽毫无关系,我没有提到这些话题。 - Dr. Jan-Philip Gehrcke
1
你可能需要再次查看你的书籍,或使用你选择的编程语言来创建基本的IP连接。服务器端:首先创建一个套接字,将其设置为侦听模式(使其处于被动状态!),开始接受传入的连接。最初创建的套接字并不会因此而改变,它仍然是被动的。所有内容都包含在我的答案中,只需阅读即可。 - Dr. Jan-Philip Gehrcke
1
无论你谈论什么被动套接字,都似乎没有帮助到OP。OP的问题是“WebSocket服务器如何处理多个传入连接请求?”他指出:“服务器的80端口将被WebSocket协议垄断。HTTP在80端口上不再工作。” 被动套接字(或称监听/接受套接字)的概念完全解决了他的谜团。 - Dr. Jan-Philip Gehrcke
@jfriend00 你太棒了!谢谢!我会花些时间来深入理解。希望以后不再为此苦恼。 :) - smwikipedia
显示剩余8条评论

5

回答你的问题:同时进行的Websocket和HTTP连接到80端口的处理方式与同时进行的HTTP连接到80端口的处理方式完全相同!

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

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

如果你想知道HTTP 1.1和UPGRADE请求在所有这些过程中扮演了什么角色,这个MSDN文章对此非常清楚:

WebSocket协议分为两部分:握手以建立升级连接,然后进行实际数据传输。首先,客户端使用“Upgrade: websocket”和“Connection: Upgrade”标头以及一些特定于协议的标头请求websocket连接,以建立正在使用的版本并设置握手。如果服务器支持该协议,则会回复相同的“Upgrade: websocket”和“Connection: Upgrade”标头并完成握手。成功完成握手后,数据传输开始。
通常情况下,WebSocket服务不会内置在Web服务器中,因此不会真正意义上的监听80端口,而是通过Web服务器的透明转发来访问。Apache Web服务器使用mod_proxy_wstunnel实现这一点。
当然,您也可以使用内置Web套接字实现的Web服务器,例如Apache Tomcat
这里的重点是: Websocket协议不是HTTP协议。它具有不同的目的。它是一种独立的应用层通信协议,也建立在TCP之上(尽管TCP不是必须的要求,但它是符合Websockets应用层协议要求的传输层协议)。
Websocket服务是一个并行运行的服务,与web服务器服务一起运行。
它使用Websocket协议,现代Web浏览器支持实现客户端接口。
建立或构建Websocket服务是为了在Websocket客户端(通常是Web浏览器)和该服务之间建立持久的非HTTP连接。
主要优点是: Websocket服务可以在需要时向客户端发送消息(“你的朋友已连接!”“你的团队刚刚进球了!”),而不必等待客户端明确的请求更新。
您可以使用HTTP 1.1建立持久连接,但HTTP不适用于除提供一组资源“按请求”并关闭连接之外的任何其他事情。
直到最近,在所有主要浏览器中都支持Websockets之前,您只有两个选择来实现Web应用程序的实时更新:
  • 实现AJAX长轮询请求,这是一个痛苦且低效的过程。

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

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

通常情况下,所有连接到特定TCP套接字(server_ip:service_TCP_port)上的服务都将在分配唯一的client_ip:client_TCP_port对时进行区分,其中client_TCP_port是由客户端在其可用TCP端口中随机选择的。

如果您仍然对Websocket连接握手时发生的HTTP-> Websocket应用程序协议切换以及它与底层TCP连接无关有疑问,我建议您参考Jan-Philip Gehrcke的答案,这非常清晰和有指导意义,可能正是您想要的。

如果随机选择端口,通信如何通过防火墙?或者通信仍然通过TCP 80端口进行,WebSocket端口被封装在其中。监听端口80的Web服务器将检查封装的端口并将其转发到WebSocket服务? - smwikipedia
不是每个套接字都有一个新的进程,但确实有多个进程。 - NotGaeL
无论如何,你是对的,我已经更新了我的回答来指出这一点。 - NotGaeL
如果我使用WebSocket的端口与80不同,是否会破坏使用现有Web/HTTP基础设施的整个目的?这样就不再适合名为WebSocket了。如果我在其他端口上使用WebSocket,为什么不直接使用TCP呢?或者WebSocket协议本身是否有特殊的好处? - smwikipedia
此外,它还可以让您在不同技术之间进行切换:通常会使用nodejs websockets服务来为使用Drupal或WordPress等PHP CMS的站点提供服务。 为什么? 因为nodejs非常擅长实时和Websockets,而PHP上的Websockets在这一点上有些不足(尽管正在取得进展,并且已经接近标准库了:http://socketo.me/)。 - NotGaeL
显示剩余11条评论

2
这是一个非常简单的概念,让我用简单的语言描述一下,以便其他一些迷失的灵魂不必阅读那些冗长的解释也能理解它。 多个连接 网络服务器开始监听连接。具体过程如下:
1.网络服务器的主进程在端口80上打开一个处于listen状态的被动套接字,例如9.9.9.9:80(其中9.9.9.9是服务器IP地址,80是端口号)。
2.浏览器向服务器的80端口发送请求。具体过程如下:
- 操作系统(简称OS)在客户端上分配一个随机的出站端口,比如:1.1.1.1:6747(其中1.1.1.1是客户端IP地址,6747是随机端口号)。 - OS发送一个带有源地址为1.1.1.1:6747和目标地址为9.9.9.9:80的数据包。该数据包通过各种路由器和交换机,最终到达目标服务器。
3.服务器接收到数据包。具体过程如下:
- 服务器的操作系统检测到数据包的目标地址是其自己的IP地址之一,并根据目标端口将其传递给与端口80相关联的应用程序。 - 网络服务器的主进程接受连接并创建一个新的活动套接字。然后通常会派生一个新的子进程,该子进程接管活动套接字。被动套接字保持打开状态以接受新的传入连接。
现在,服务器发送到客户端的每个数据包都将具有以下地址:
  • 源: 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连接。但是只有在以下情况下才有可能实现:
  1. WebSocket支持HTTP动词/请求

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

第一个不在协议的范围内,第二个会破坏现有的Web基础设施。因为客户端/浏览器可以与同一服务器建立多个HTTP连接,将其中一些从HTTP切换到WebSocket既保留了相同的80端口,又允许了与HTTP不同的协议。切换通过客户端发起的协议握手完成。

1
我使用Wireshark分析了浏览器和服务器之间的TCP数据包流。服务器端没有选择任意的出站端口(例如您示例中的1553端口)。只有客户端使用随机端口。服务器始终使用其监听的端口(在我的情况下是8080端口)。因此,我不同意您的第三个观察结果。 - smwikipedia
1
这个答案是不正确的。服务器不会为传入连接分配新端口,而是使用与监听端口相同的端口。 - user207421
1
抱歉造成困惑。我刚刚更正了答案。我错误地将端口与套接字相关联。服务器需要为每个连接打开一个新的套接字,而不是端口,并且每个新套接字中的目标端口保持不变。 - Greg
1
你的第一点包含了一个“非因果关系”:子进程的数量与是否存在处于LISTEN状态的端口没有任何关系。在第二点中,你完全忽略了HTTP keepalive作为默认设置。在第三点中,元组描述了连接,而不仅仅是套接字,内核需要整个元组,而不仅仅是其中的客户端部分。这里有太多错误和太多无关紧要的废话,没有回答问题。 - user207421
1
@EJP,删除了不回答问题的废话。现在它变得简短而简洁。还有其他错误吗?感谢反馈。我本来不打算给出详细的答案,因为那需要书中的几页。但我同意,跳过废话可能比提供误导性信息更好。 - Greg
显示剩余3条评论

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