低延迟HTML5 WebSocket服务器,用于将实时和计算数据流式传输到Web浏览器网格(SignalR的一种形式)。






4.91/5 (68投票s)
这是一个 HTML5 WebSocket 服务,可将实时数据流式传输到浏览器网格控件。该服务能够实时(动态在运行时)自动计算数学表达式。
下载次数
快速演示(32 位版本)
- 即时数学表达式流 点击此处
快速演示(可在任何 Windows 操作系统版本中运行)
源代码(在 VS2012 中编译,可轻松自动迁移到其他 Visual Studio 版本)
- 源代码 点击此处
引言
实时更新到浏览器是当今世界中的一个重要场景。所有企业(银行、交易、医疗保健、零售等)都严重依赖互联网/浏览器应用程序来接触其尊贵客户。无论是股票报价、新闻还是邮件,实时更新都能为您的 Web 应用程序带来敏捷性。本文介绍了一个 html5 WebSocket 服务,该服务可将实时数据流式传输到浏览器网格。此外,该服务还可以在运行时动态计算复杂的数学表达式(公式)。例如,如果您需要实时更新诸如股票名称、代码、数量、价格、头寸(多头/空头)等项目到用户浏览器,该服务可以通过 html5 WebSocket 使用低延迟 TCP 通信来更好地处理,优于 AJAX/COMET。如果您想添加更多基于数据中其他项目计算的新项目,该 WebSocket 服务器将在后台为您处理这些工作。
假设您想添加一个新项目“总成本”(其中“总头寸 = 价格 * 数量 * 多头/空头”),此服务将自动计算它。给定数学表达式公式,此服务将在运行时在后台执行计算。如果这些还不够,它还可以即时添加新公式。假设您已将此网格部署到生产环境。有人认为您的公式是错误的,需要立即更改。只需在服务器端修改公式,所有客户端浏览器都会看到使用正确公式的实时更新。这不是很棒吗?
图 0:解释了基于 CSV 文件的数据和基于数学表达式计算的数据通过低延迟 html5 WebSocket 实时流式传输到客户端浏览器。
快速演示
本文包含两个快速演示二进制文件。
如何执行演示二进制文件
检查两个演示的通用步骤
- 下载相应的二进制文件并将其复制到一个文件夹/目录。
- 运行 wsSocket.exe - 如果防火墙弹出,请点击“允许”。
- 在您的 Web 浏览器中打开 testHTML1.html。
- 点击浏览器网页中的“Stream”按钮。
智能数据网格带公式的网格
此演示旨在展示从 CSV 文件更新到浏览器。请按照上述步骤 (1) 至 (4)。然后,执行以下步骤:
- 在编辑器(如 TextPad、Notepad)中打开 test.csv 文件。
- 修改 CSV 文件中的值并保存。
您将看到数据从 CSV 文件流式传输到浏览器。最有趣的部分是,在 test.csv 文件(已包含示例)中添加一个新的数学表达式/公式列。您将看到 html5 WebSocket 服务器自动计算该数学表达式,并将其更新到浏览器网格。
带随机数推送的网格
无需进一步步骤即可观看此演示。此演示旨在展示从 WebSocket 服务实时流式传输到客户端浏览器的实时更新。WebSocket 服务会生成随机数,并通过 Html5 推送到浏览器。
演示屏幕截图
带即时公式更新的网格演示
图 1:服务器端 CSV 文件中的数据正在流式传输到客户端 Web 浏览器。请注意公式列,其在 CSV 文件中没有数据,但由 WebSocket 服务自动计算并自动流式传输到 Web 浏览器。
带随机数推送的网格演示
图 2:通过低延迟 html5 流进行随机数推送。
使用代码
将此 WebSocket 服务集成到您的应用程序中非常简单。
- 下载 WebSocket 服务源代码((点击此处) 并将其保存到您想要的文件夹/目录。
- 使用 Visual Studio 2012(或其他版本的 Visual Studio 编译器)编译项目 webSocket。此服务器代码编译为动态链接库(webSocket.dll)。
- 在您的应用程序中,将 webSocket.dll 添加为引用。
- 在您的 Visual Studio 应用程序项目中,右键单击“引用”。单击“添加引用”>“浏览”,然后选择您在上一步中编译的目录中的文件 webSocket.dll。
- 在您的 c# 文件顶部添加以下代码:
using wsSocket;
- 然后,您只需使用以下四行代码即可开始流式传输:
<code>string </code>data = @"-1,-1, 4,5, Item_1, Item_2, Item_3, Computed_1 = Item_1 + Item_2, Computed_2 = Item_2 + Item_3, 1,2,3,,, 6,7,8,,, 9,1,0,,,"; <code>html5Stream </code>wSock = new <code>html5Stream</code>(); wSock.setStreamData(data); wSock.startServer(); // Clients will get update whenever setStreamData method is called
以上代码说明
第一行将变量 data
分配给流式内容。此内容将在稍后进行解释。下一行创建一个 type html5Stream
对象,名为 wSock。然后调用 startServer 方法来启动 WebSocket 服务器。请记住,startServer
是一个阻塞调用。因此,如果您希望流式传输实时数据,则需要在单独的线程中调用 setStreamData。
Chrome 中的输出
图 3:以上三行代码在 Google Chrome 浏览器中的输出。
传递数据的说明
第一行 -1, -1, 4, 5
-1, -1 - 预留用于将来使用。
4 - 行数。
5 - 列数。
第二行:Item_1, Item_2, Item_3, Computed_1 = Item_1 + Item_2, Computed_2 = Item_2 + Item_3
Item_1, Item_2, Item_3 - 列标题。
Computed_1, Computed_2 - 标题和计算这些列的公式。请注意,在接下来的数据行中,这些列不会填充。
所有其他行:传输到浏览器的数据。请注意,公式字段的数据不会填充,它们由智能网格自动计算。
WebSocket 安全性
html5 WebSocket 是一项不断发展的标准。安全性是使用 WebSocket 时需要考虑的关键因素之一。没有企业希望数据被中间人看到。以下是一些需要考虑的要点:
安全 WebSocket:对于生产环境,强制使用 wss(安全 WebSocket)而不是 ws。这可以防止您免受中间人攻击。
客户端输入:由于 WebSocket 是 TCP 连接,请务必验证您从客户端收到的缓冲区。此类验证可能会在您的设计中增加一些开销,但它为您提供了更大的优势,可以防止 SQL 注入、缓冲区溢出等攻击。
避免暴露服务:在没有适当身份验证/授权的情况下,避免通过 WebSocket 暴露其他服务。WebSocket 目前不支持身份验证/授权。HTTP 连接需要在升级连接到 WebSocket 之前执行此工作。
不要信任 Origin 标头:WebSocket 连接在标头信息中包含 Origin 连接详细信息。不要根据此 Origin 信息做出重大决定。恶意客户端可以伪装成标准 Web 浏览器。
工作原理
Ajax,简要概述
浏览器充当请求-响应客户端。通常,浏览器客户端从服务器请求资源(如 HTML)。服务器(又名 Web 服务器)返回资源。然后就结束了(即连接关闭!)。随着越来越多的企业将其应用程序迁移到浏览器,出现了新的场景(用例),需要响应式 Web 应用程序。我们都喜欢在搜索框中输入时出现的 Google 建议,对吧?这是 AJAX 模式的一个很好的应用。以下是 AJAX 的简要说明:
图 4:AJAX 工作原理的简单说明。
如上图所示,AJAX 调用由浏览器在后台进行,即网页的一部分在不重新加载整个页面的情况下进行更新。通常,AJAX 消息很小。AJAX 调用使用一种称为 XMLHttpRequest 的特殊请求类型。当从服务器收到响应时,此信息会呈现给用户,而无需刷新整个页面。对用户来说,它看起来像是网页即时响应,而无需调用服务器。AJAX 适用于用户创建的事件。在此解释中,用户在网页(如 Google 搜索框)中输入以发起 AJAX 调用到服务器。考虑另一个用例,例如,实时数据网格用例:AJAX 不是一个合适的解决方案。因为在实时数据馈送中,服务器会创建事件。也就是说,服务器会知道何时到达新数据。客户端进行基于计时器的 AJAX 调用会不必要地增加网络流量,更不用说耗尽所有服务器资源。如果传递的数据量很大,那么服务器会进入一个无法接受任何新连接的状态。
COMET,简要概述
基于计时器的 AJAX 调用会使服务器因不必要的调用而堵塞。为了解决这个问题,COMET(又称反向 AJAX)被广泛使用。反向 AJAX/COMET 允许服务器推送数据,而不是客户端在特定间隔内轮询数据。这非常适合服务器发起的事件用例,例如向浏览器进行实时馈送。
图 5:COMET / 反向 AJAX 工作原理的简单说明。
如上图所示,客户端对服务器的调用由服务器挂起。挂起与断开连接不同。挂起调用意味着服务器将这些连接保持挂起状态。当有新数据可供更新时,服务器会通过这些挂起的连接发送响应。这比 AJAX 好得多,但并不完美。它存在错误处理不可靠、连接数限制、延迟问题等问题。
HTML5 WebSocket,一种低延迟、可靠的通信标准
简单来说,浏览器和服务器之间的同一个 http 连接被升级为 TCP,这是一种可靠的协议。一种漂亮、简单、干净的方法。这允许浏览器和服务器之间进行全双工通信。这消除了过程中涉及的所有中间人(我是指额外的层)。HTML5 在浏览器和服务器之间提供了良好的低延迟、可靠的 TCP 通信。更重要的是,由于这是一个标准,要求是:它应该在所有浏览器中运行。今天,每个公司都在努力拥抱 HTML5。
图 6:简单说明客户端浏览器和服务器之间如何建立 html5 握手。
HTML5 WebSocket 的工作原理
HTML5 WebSocket 的工作原理是客户端发起与服务器的连接。通常,Web 浏览器会尝试连接到一个公开 WebSocket 接口的服务器。初始握手就像下面的图片所示一样简单。当我们遇到一个人时,我们会说“你好吗?”并握手。握手时,对方会说“你好,很高兴认识你”。完成此初始握手后,对话开始。双方都可以发起新话题。同样,初始握手后,http 协议将升级为 TCP。然后,通信变成低延迟、全双工 TCP,如下图 7 所示。
图 7:html5 WebSocket 握手步骤。
例如,这是来自 Chrome 浏览器的示例。以下是浏览器发送给握手感兴趣的服务器的内容:
GET /service HTTP/1.1 Host: localhost:8080 Connection: Upgrade Pragma: no-cache Cache-Control: no-cache Upgrade: websocket Origin: null Sec-WebSocket-Version: 13 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.101 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: en-US,en;q=0.8 Sec-WebSocket-Key: 198ol8E3A0P/DPNlSOq4XA== Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits // copied from chrome browser request
以下是服务器的回复:
HTTP/1.1 101 Switching Protocols\r\n Upgrade: websocket\r\nConnection: Upgrade\r\n Sec-WebSocket-Accept: n72SJe/JO84rHrLBkigtRDc+6QA=\r\n\r\n
我故意让 '\r\n' 在消息中可读。请注意,最后一行以两个这样的字符结尾。它们对于浏览器接受服务器的握手很重要。完成此握手后,服务器和客户端已准备好进行全双工、低延迟通信。
代码说明
让我们看看如何建立基本的 html5 连接。html5Stream.StartServer
是一个简单的方法,它接受客户端连接并异步处理它。此方法还会生成一个单独的线程来处理所有客户端发布工作。我们稍后将对此方法进行介绍。目前,此方法不具备 html5 的重要性。它只是一个简单的套接字接受,仅此而已。Html5Stream.AcceptConnection
执行必要的 html5 特定握手与客户端进行交互。
<code>private void</code> OnAcceptConnection(object param)
{
------------ // code skipped for brevity
------------
int <code>rcvBytes </code>= client.Receive(buffer);
headerResponse = getStrFromByte(buffer, rcvBytes);
if (client != null)
{
prepareClientResponse(headerResponse, ref sendBuffer);
client.Send(sendBuffer);
rcvBytes = client.Receive(buffer);
string dataFromClient = decodeData(buffer);
subscribe(client);
eventDataAvailable.Set();
}
-------
-------
}
此函数执行将 http 升级到 tcp 连接所需的必要握手。
建立 html5 握手的步骤
- 创建一个套接字并绑定到特定端口。
- 建立连接后,浏览器将发送如下请求。旧浏览器可能发送略有不同的数据。一些不同的浏览器可能发送略有不同的数据。因此,如果您编写“专业级”代码,则需要解决所有这些情况。请参阅参考文献 (2)。
- 基于握手详细信息,需要生成答案密钥。请查看
html5Stream.prepareClientResponse
方法。此方法从服务器准备连接客户端的握手响应。握手响应如下所示。如果您阅读,令人惊讶的是,它非常简单。就这样,握手完成。之后,客户端和服务器可以享受彼此之间低延迟、可靠、全双工的通信。
现在,您可能想知道,这有多简单,对吗?
一切都应该尽可能简单,但不要过于简单。
-- 阿尔伯特·爱因斯坦
因此,为了避免过于简单,消息不是原始的。服务器和客户端之间的通信消息使用数据帧(记住,爱因斯坦说,不要过于简单)。开玩笑,正如在参考文献 (3) 中所述,使用数据帧是为了避免混淆网络中间设备和出于安全原因。即使对于 SSL/TLS,也会执行这种掩蔽。幸运的是,实现起来并不难。我实现了我们网格需要的部分(为了简洁)。请查看 html5Stream.encodeData
和 html5Stream.decodeData
方法。
现在握手和编码已完成,我们已准备好流式传输数据。html5Stream.publishAll
方法执行此工作。它等待 dataReceived
事件。当用户调用 html5Stream.setStreamData
时,此事件就会被设置。也就是说,当数据准备好发布到客户端时。可用数据将被编码并发送到客户端。列表中的每个客户端都会被枚举并发送新的更新。
private void publishAll(object dummy) { byte[] byteToSend = null; ----------- // lines skipped for simplicity ----------- while(!shutDown) { bool dataReceived = eventDataAvailable.WaitOne(1000); if (dataReceived) { encodeData(getStreamData(), ref byteToSend); lock (lockObj) { HashSet<Socket>.Enumerator enumSockets = socketsToUpdate.GetEnumerator(); SocketAsyncEventArgs sendData = new SocketAsyncEventArgs(); sendData.SetBuffer(byteToSend, 0, byteToSend.Length); while (enumSockets.MoveNext()) { enumSockets.Current.SendAsync(sendData); } } ---------- ---------- // lines skipped for brevity } } }
关注点
=> 此实现旨在用于学习目的。因此,代码的简洁性和简单性贯穿整个代码,以便于理解,而不是追求性能。企业级源代码将使用完全不同的数据结构、接口、错误/异常处理等。如果您对专业/企业级库感兴趣,请联系我。
=> 免费文件流工具已添加到示例项目中。打开 CSV 文件,添加新列、行。也可以尝试添加新公式。看到动态更新在另一台机器的浏览器屏幕上生成是相当有趣的。添加新的计算列从未如此简单。
我真诚地感谢...
感谢您阅读本文。我希望听到您的反馈。如果您喜欢这篇文章,请在页面顶部为本文投票,并在底部留下您的评论。
非常感谢muParser,这是此网格中使用的数学解析库。它是一个非常快速的表达式解析库。感谢https://creately.com 和 https://cacoo.com。我使用他们的在线工具为本文制作了图表。
参考文献
历史
- 2015年3月24日 - 第一个版本
- 2015年3月25日
- 修改了标题以包含流式服务
- 添加了有意义的参考文献标题
- 2015年3月29日 - 更新了图片以匹配屏幕尺寸
- 2015年3月30日 - 更新了源代码中的解码逻辑。修复了解码逻辑中的错误。注释掉了之前的代码以供参考。
- 2015年4月1日 - 添加了“WebSocket 安全性”部分。
- 2015年4月7日 - 移动了“WebSocket 安全性”部分。
- 2015年4月16日 - 添加了新章节“HTML5 WebSocket 的工作原理”。
- 2015年4月18日 - 修改了上一版本中的 fig0 图片问题。
- 2015年8月18日 - 根据要求添加了 32 位版本演示。