WebSocket 快速指南



WebSocket - 概述

从字面上讲,握手可以定义为两个人为了表示问候、祝贺、同意或告别而握手的动作。在计算机科学中,握手是一个确保服务器与其客户端同步的过程。握手是 WebSocket 协议的基本概念。

下图显示了服务器与各个客户端的握手过程:

Server

WebSocket – 定义

WebSocket 定义为服务器和客户端之间的双向通信,这意味着双方可以同时通信和交换数据。

WebSocket 的关键点是真正的并发性性能优化,从而产生更具响应性和更丰富的 Web 应用程序。

WebSocket 协议描述

此协议从根本上定义了全双工通信。WebSocket 在将桌面丰富的功能带入 Web 浏览器方面向前迈进了一步。它代表了客户端/服务器 Web 技术长期以来期待的进步。

WebSocket 的主要特性如下:

  • WebSocket 协议正在标准化,这意味着借助此协议可以实现 Web 服务器和客户端之间的实时通信。

  • WebSocket 正在转变为客户端和服务器之间实时通信的跨平台标准。

  • 此标准支持新型应用程序。借助这项技术,实时 Web 应用程序的业务可以加速发展。

  • WebSocket 最大的优点是它通过单个 TCP 连接提供双向通信(全双工)。

URL

HTTP 具有自己的一组方案,例如 http 和 https。WebSocket 协议在其 URL 模式中也定义了类似的方案。

下图显示了 WebSocket URL 的标记。

Protocol

浏览器支持

WebSocket 协议的最新规范定义为RFC 6455 – 一项提议的标准。

RFC 6455 受各种浏览器支持,例如 Internet Explorer、Mozilla Firefox、Google Chrome、Safari 和 Opera。

WebSocket - 双向通信

在深入探讨 WebSocket 的需求之前,有必要了解一下现有的技术,这些技术用于服务器和客户端之间的双向通信。它们如下:

  • 轮询
  • 长轮询
  • 流式传输
  • 回发和 AJAX
  • HTML5

轮询

轮询可以定义为一种方法,无论传输中是否存在数据,它都会执行周期性请求。周期性请求以同步方式发送。客户端在指定的时间间隔内向服务器发送周期性请求。服务器的响应包括可用数据或某些警告消息。

长轮询

长轮询顾名思义,包括类似于轮询的技术。客户端和服务器保持连接处于活动状态,直到获取某些数据或超时发生。如果由于某些原因连接丢失,客户端可以重新开始并执行顺序请求。

长轮询只不过是轮询过程的性能改进,但持续的请求可能会减慢该过程。

流式传输

它被认为是实时数据传输的最佳选择。服务器保持与客户端的连接打开并处于活动状态,直到获取所需数据为止。在这种情况下,连接被认为是无限期打开的。流式传输包括 HTTP 标头,这会增加文件大小,从而增加延迟。这可以被认为是一个主要的缺点。

AJAX

AJAX 基于 JavaScript 的XmlHttpRequest 对象。它是异步 JavaScript 和 XML 的缩写形式。XmlHttpRequest 对象允许执行 JavaScript 而不重新加载整个网页。AJAX 只发送和接收网页的一部分。

使用XmlHttpRequest 对象的 AJAX 调用的代码片段如下:

var xhttp;

if (window.XMLHttpRequest) {
   xhttp = new XMLHttpRequest();
} else {
   // code for IE6, IE5
   xhttp = new ActiveXObject("Microsoft.XMLHTTP");
}

WebSocket相比,AJAX的主要缺点是:

  • 它们发送 HTTP 标头,这会使总大小更大。
  • 通信是半双工的。
  • Web 服务器消耗更多资源。

HTML5

HTML5 是一个用于开发和设计 Web 应用程序的强大框架。其主要支柱包括标记、CSS3JavaScript API。

下图显示了 HTML5 组件:

HTML5

以下代码片段描述了 HTML5 及其文档类型的声明。

<!DOCTYPE html>

为什么我们需要 WebSocket?

互联网最初被设想为超文本标记语言 (HTML) 页面的集合,这些页面相互链接以形成一个概念性的信息网络。随着时间的推移,静态资源的数量增加,更丰富的项目(例如图像)也开始成为网络结构的一部分。

服务器技术进步,允许动态服务器页面——内容基于查询生成的页面。

很快,对更动态网页的需求导致了动态超文本标记语言 (DHTML) 的出现。这都要感谢 JavaScript。在接下来的几年里,我们看到了跨框架通信,试图避免页面重新加载,然后是框架内的HTTP 轮询

但是,这些解决方案都没有提供真正标准化的跨浏览器解决方案,用于服务器和客户端之间的实时双向通信。

这导致了对 WebSocket 协议的需求。它催生了全双工通信,将桌面丰富的功能带到所有 Web 浏览器。

WebSocket – 功能

WebSocket 代表着 Web 通信历史上的一次重大升级。在其存在之前,Web 客户端和服务器之间的所有通信都仅依赖于 HTTP。

WebSocket 有助于持久全双工连接的动态流动。全双工是指来自两端的通信速度相当快。

它被称为改变游戏规则者,因为它有效地克服了现有协议的所有缺点。

面向开发人员和架构师的 WebSocket

WebSocket 对开发人员和架构师的重要性:

  • WebSocket 是一种独立的基于 TCP 的协议,但它旨在支持任何其他协议,这些协议传统上只能在纯 TCP 连接之上运行。

  • WebSocket 是一个传输层,在其之上可以运行任何其他协议。WebSocket API 支持定义子协议:可以解释特定协议的协议库。

  • 此类协议的示例包括 XMPP、STOMP 和 AMQP。开发人员不再需要考虑 HTTP 请求-响应范例。

  • 浏览器端唯一的要求是运行可以解释 WebSocket 握手、建立和维护 WebSocket 连接的 JavaScript 库。

  • 在服务器端,行业标准是使用在 TCP 之上运行并利用 WebSocket 网关的现有协议库。

下图描述了 WebSocket 的功能:

Web

WebSocket 连接是通过 HTTP 启动的;HTTP 服务器通常将 WebSocket 握手解释为升级请求。

WebSocket 可以作为现有 HTTP 环境的补充附加组件,也可以提供添加 Web 功能所需的架构。它依赖于更高级的全双工协议,允许数据在客户端和服务器之间双向流动。

WebSocket 的功能

WebSocket 在 Web 服务器和客户端之间建立连接,以便双方都可以开始发送数据。

建立 WebSocket 连接的步骤如下:

  • 客户端通过称为 WebSocket 握手的过程建立连接。

  • 该过程从客户端向服务器发送常规 HTTP 请求开始。

  • 请求升级标头。在此请求中,它通知服务器请求用于 WebSocket 连接。

  • WebSocket URL 使用ws 方案。它们也用于安全的 WebSocket 连接,相当于 HTTPS。

初始请求标头的简单示例如下:

GET ws://websocket.example.com/ HTTP/1.1
Origin: http://example.com
Connection: Upgrade
Host: websocket.example.com
Upgrade: websocket

WebSocket – 实现

WebSocket 不仅在 Web 领域,而且在移动行业中也扮演着关键角色。WebSocket 的重要性如下所示。

  • 顾名思义,WebSocket 与 Web 相关。Web 由一些浏览器的技术组成;它是一个广泛的通信平台,用于大量的设备,包括台式电脑、笔记本电脑、平板电脑和智能手机。

  • 使用 WebSocket 的 HTML5 应用程序可以在任何支持 HTML5 的 Web 浏览器上运行。

  • 主流操作系统都支持 WebSocket。移动行业的每个主要参与者都在自己的原生应用程序中提供 WebSocket API。

  • WebSocket 被称为全双工通信。WebSocket 的方法适用于某些类别的 Web 应用程序,例如聊天室,其中客户端和服务器的更新同时共享。

Web Socket

WebSocket 作为 HTML5 规范的一部分,允许 Web 页面和远程主机之间进行全双工通信。该协议旨在实现以下好处,这些好处可以被认为是关键点:

  • 通过单个连接(而不是两个连接)使用全双工减少不必要的网络流量和延迟

  • 通过代理和防火墙进行流式传输,同时支持上行和下行通信。

WebSocket – 事件和操作

为了在它们之间进行通信,需要从客户端初始化到服务器的连接。为了初始化连接,需要使用远程或本地服务器的 URL 创建 JavaScript 对象。

var socket = new WebSocket(“ ws://echo.websocket.org ”);

上述 URL 是一个公共地址,可用于测试和实验。websocket.org 服务器始终处于运行状态,当它接收到消息时会将其发送回客户端。

这是确保应用程序正常运行的最重要步骤。

WebSockets – 事件

Web Socket API 主要有四个事件

  • 打开 (Open)
  • 消息 (Message)
  • 关闭 (Close)
  • 错误 (Error)

每个事件都通过实现相应的函数来处理,例如onopen、onmessage、oncloseonerror 函数。也可以通过 addEventListener 方法来实现。

事件和函数的简要概述如下:

打开 (Open)

一旦客户端和服务器之间建立连接,Web Socket 实例就会触发 open 事件。这被称为客户端和服务器之间的初始握手。连接建立后触发的事件称为onopen

消息 (Message)

当服务器发送一些数据时,通常会发生消息事件。服务器发送给客户端的消息可以包括纯文本消息、二进制数据或图像。每当发送数据时,都会触发onmessage 函数。

关闭 (Close)

关闭事件标志着服务器和客户端之间通信的结束。可以使用onclose 事件来关闭连接。使用onclose 事件标志通信结束之后,服务器和客户端之间将无法再传输任何消息。关闭事件也可能由于连接不良而发生。

错误 (Error)

错误标志着通信过程中发生的某些错误。它通过onerror 事件来标记。onerror 事件之后总是会终止连接。每个事件的详细描述将在后面的章节中讨论。

WebSockets – 动作

事件通常在发生某些事情时触发。另一方面,当用户想要发生某些事情时,就会采取行动。动作是由用户使用函数进行显式调用的。

WebSocket 协议支持两种主要动作:

  • send()
  • close()

send()

此操作通常用于与服务器进行通信,包括发送消息,其中包括文本文件、二进制数据或图像。

使用 send() 操作发送的聊天消息如下:

// get text view and button for submitting the message
var textsend = document.getElementById(“text-view”);
var submitMsg = document.getElementById(“tsend-button”);

//Handling the click event
submitMsg.onclick = function ( ) {
   // Send the data
   socket.send( textsend.value);
}

注意 – 只有在连接打开时才能发送消息。

close()

此方法代表“再见”握手。它会完全终止连接,在重新建立连接之前,无法传输任何数据。

var textsend = document.getElementById(“text-view”);
var buttonStop = document.getElementById(“stop-button”);

//Handling the click event
buttonStop.onclick = function ( ) {
   // Close the connection if open
   if (socket.readyState === WebSocket.OPEN){
      socket.close( );
   }
}

也可以使用以下代码片段故意关闭连接:

socket.close(1000,”Deliberate Connection”);

WebSockets – 打开连接

一旦客户端和服务器之间建立连接,Web Socket 实例就会触发 open 事件。这被称为客户端和服务器之间的初始握手。

连接建立后触发的事件称为onopen。创建 WebSocket 连接非常简单。您只需调用WebSocket 构造函数并传入服务器的 URL 即可。

以下代码用于创建 WebSocket 连接:

// Create a new WebSocket.
var socket = new WebSocket('ws://echo.websocket.org');

连接建立后,open 事件将在您的 WebSocket 实例上触发。

onopen 指的是客户端和服务器之间的初始握手,导致了第一次交易,并且 Web 应用程序已准备好传输数据。

以下代码片段描述了打开 WebSocket 协议连接:

socket.onopen = function(event) {
   console.log(“Connection established”);
   // Display user friendly messages for the successful establishment of connection
   var.label = document.getElementById(“status”);
   label.innerHTML = ”Connection established”;
}

最好向等待建立 WebSocket 连接的用户提供适当的反馈。但是,需要注意的是,WebSocket 连接速度相对较快。

建立的 WebSocket 连接演示文档位于以下 URL:https://www.websocket.org/echo.html

连接建立和对用户的响应快照如下所示:

Snapshot

建立打开状态允许全双工通信和消息传输,直到连接终止。

示例

构建客户端 HTML5 文件。

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
	
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
	
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
			
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
      }
	
      function onOpen(evt) {
         writeToScreen("CONNECTED");
      }
	
      window.addEventListener("load", init, false);
   
   </script>

   <h2>WebSocket Test</h2>
   <div id = "output"></div>

</html>

输出如下:

Connected

上述 HTML5 和 JavaScript 文件显示了 WebSocket 的两个事件的实现:

  • onLoad 用于创建 JavaScript 对象和初始化连接。

  • onOpen 建立与服务器的连接并发送状态。

WebSockets – 处理错误

一旦客户端和服务器之间建立连接,Web Socket 实例就会触发open 事件。错误是针对通信过程中发生的错误生成的。它通过onerror 事件来标记。onerror 事件之后总是会终止连接。

当通信过程中出现错误时,会触发onerror 事件。onerror 事件之后是连接终止,即close 事件。

最佳实践是始终告知用户意外错误并尝试重新连接。

socket.onclose = function(event) {
   console.log("Error occurred.");
	
   // Inform the user about the error.
   var label = document.getElementById("status-label");
   label.innerHTML = "Error: " + event;
}

在错误处理方面,您必须同时考虑内部和外部参数。

  • 内部参数包括由于代码中的错误或意外的用户行为而可能生成的错误。

  • 外部错误与应用程序无关;相反,它们与无法控制的参数有关。最重要的一项是网络连接。

  • 任何交互式双向 Web 应用程序都需要活跃的互联网连接。

检查网络可用性

假设您的用户正在享受您的 Web 应用程序,突然在他们任务的中间网络连接变得无响应。在现代原生桌面和移动应用程序中,检查网络可用性是一项常见任务。

最常见的方法是简单地向应该处于活动状态的网站(例如,http://www.google.com)发出 HTTP 请求。如果请求成功,则桌面或移动设备知道存在活动连接。同样,HTML 具有XMLHttpRequest 用于确定网络可用性。

但是,HTML5 使其变得更加容易,并引入了一种方法来检查浏览器是否可以接受 Web 响应。这是通过 navigator 对象实现的:

if (navigator.onLine) {
   alert("You are Online");
}else {
   alert("You are Offline");
}

脱机模式意味着设备未连接或用户已从浏览器工具栏中选择了脱机模式。

以下是关于如何在发生 WebSocket 关闭事件时告知用户网络不可用并尝试重新连接的方法:

socket.onclose = function (event) {
   // Connection closed.
   // Firstly, check the reason.
	
   if (event.code != 1000) {
      // Error code 1000 means that the connection was closed normally.
      // Try to reconnect.
		
      if (!navigator.onLine) {
         alert("You are offline. Please connect to the Internet and try again.");
      }
   }
}

接收错误消息的演示

以下程序说明了如何使用 WebSockets 显示错误消息:

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
		
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
		
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
			
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
			
         websocket.onclose = function(evt) {
            onClose(evt)
         };
			
         websocket.onerror = function(evt) {
            onError(evt)
         };
      }
		
      function onOpen(evt) {
         writeToScreen("CONNECTED");
         doSend("WebSocket rocks");
      }
		
      function onClose(evt) {
         writeToScreen("DISCONNECTED");
      }
		
      function onError(evt) {
         writeToScreen('<span style = "color: red;">ERROR:</span> ' + evt.data);
      } 
		
      function doSend(message) {
         writeToScreen("SENT: " + message); websocket.send(message);
      }
		
      function writeToScreen(message) {
         var pre = document.createElement("p"); 
         pre.style.wordWrap = "break-word"; 
         pre.innerHTML = message; output.appendChild(pre);
      }
		
      window.addEventListener("load", init, false);
   </script>
	
   <h2>WebSocket Test</h2>
   <div id = "output"></div>
	
</html>

输出如下:

Disconnected

WebSockets – 发送和接收消息

消息事件通常在服务器发送一些数据时发生。服务器发送给客户端的消息可以包括纯文本消息、二进制数据或图像。每当发送数据时,都会触发onmessage 函数。

此事件充当客户端对服务器的“耳朵”。每当服务器发送数据时,都会触发onmessage 事件。

以下代码片段描述了打开 WebSocket 协议连接。

connection.onmessage = function(e){
   var server_message = e.data;
   console.log(server_message);
}

还需要考虑可以使用 WebSockets 传输哪些类型的数据。WebSocket 协议支持文本和二进制数据。在 Javascript 中,text 指的是字符串,而二进制数据表示为ArrayBuffer

WebSockets 每次只支持一种二进制格式。二进制数据的声明如下所示:

socket.binaryType = ”arrayBuffer”;
socket.binaryType = ”blob”;

字符串

字符串被认为是有用的,用于处理人类可读的格式,例如 XML 和 JSON。每当触发onmessage 事件时,客户端都需要检查数据类型并采取相应措施。

确定数据类型为字符串的代码片段如下所示:

socket.onmessage = function(event){

   if(typeOf event.data === String ) {
      console.log(“Received data string”);
   }
}

JSON(JavaScript 对象表示法)

这是一种轻量级格式,用于在计算机之间传输人类可读的数据。JSON 的结构由键值对组成。

示例

{
   name: “James Devilson”,
   message: “Hello World!”
}

以下代码显示了如何处理 JSON 对象并提取其属性:

socket.onmessage = function(event) {
   if(typeOf event.data === String ){
      //create a JSON object
      var jsonObject = JSON.parse(event.data);
      var username = jsonObject.name;
      var message = jsonObject.message;
		
      console.log(“Received data string”);
   }
}

XML

XML 解析并不困难,尽管技术因浏览器而异。最好的方法是使用 jQuery 等第三方库进行解析。

在 XML 和 JSON 中,服务器都以字符串形式响应,该字符串在客户端进行解析。

ArrayBuffer

它包含结构化二进制数据。包含的位按顺序排列,以便可以轻松跟踪位置。ArrayBuffer 方便存储图像文件。

使用 ArrayBuffer 接收数据相当简单。使用instanceOf 运算符而不是等于运算符。

以下代码显示了如何处理和接收 ArrayBuffer 对象:

socket.onmessage = function(event) {
   if(event.data instanceof ArrayBuffer ){
      var buffer = event.data;
      console.log(“Received arraybuffer”);
   }
}

演示应用程序

以下程序代码显示了如何使用 WebSockets 发送和接收消息。

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
		
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
		
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
			
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
		
         websocket.onmessage = function(evt) {
            onMessage(evt)
         };
		
         websocket.onerror = function(evt) {
            onError(evt)
         };
      }
		
      function onOpen(evt) {
         writeToScreen("CONNECTED");
         doSend("WebSocket rocks");
      }
		
      function onMessage(evt) {
         writeToScreen('<span style = "color: blue;">RESPONSE: ' +
            evt.data+'</span>'); websocket.close();
      }

      function onError(evt) {
         writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
      }
		
      function doSend(message) {
         writeToScreen("SENT: " + message); websocket.send(message);
      }
		
      function writeToScreen(message) {
         var pre = document.createElement("p"); 
         pre.style.wordWrap = "break-word"; 
         pre.innerHTML = message; output.appendChild(pre);
      }
		
      window.addEventListener("load", init, false);
		
   </script>
	
   <h2>WebSocket Test</h2>
   <div id = "output"></div> 
	
</html>

输出如下所示。

WebSocket Rocks

WebSockets – 关闭连接

关闭事件标志着服务器和客户端之间通信的结束。可以使用onclose 事件来关闭连接。使用onclose 事件标志通信结束之后,服务器和客户端之间将无法再传输任何消息。关闭事件也可能由于连接不良而发生。

close() 方法代表“再见”握手。它会终止连接,除非连接再次打开,否则无法交换任何数据。

与之前的示例类似,当用户单击第二个按钮时,我们将调用close() 方法。

var textView = document.getElementById("text-view");
var buttonStop = document.getElementById("stop-button");

buttonStop.onclick = function() {
   // Close the connection, if open.
   if (socket.readyState === WebSocket.OPEN) {
      socket.close();
   }
}

也可以传递我们前面提到的代码和原因参数,如下所示。

socket.close(1000, "Deliberate disconnection");

以下代码全面概述了如何关闭或断开 WebSocket 连接:

<!DOCTYPE html>
<html>
   <meta charset = "utf-8" />
   <title>WebSocket Test</title>

   <script language = "javascript" type = "text/javascript">
      var wsUri = "ws://echo.websocket.org/";
      var output;
	
      function init() {
         output = document.getElementById("output");
         testWebSocket();
      }
	
      function testWebSocket() {
         websocket = new WebSocket(wsUri);
		
         websocket.onopen = function(evt) {
            onOpen(evt)
         };
		
         websocket.onclose = function(evt) {
            onClose(evt)
         };
		
         websocket.onmessage = function(evt) {
            onMessage(evt)
         };
		
         websocket.onerror = function(evt) {
            onError(evt)
         };
      }
	
      function onOpen(evt) {
         writeToScreen("CONNECTED");
         doSend("WebSocket rocks");
      }
	
      function onClose(evt) {
         writeToScreen("DISCONNECTED");
      }
	
      function onMessage(evt) {
         writeToScreen('<span style = "color: blue;">RESPONSE: ' + 
            evt.data+'</span>'); websocket.close();
      }
	
      function onError(evt) {
         writeToScreen('<span style = "color: red;">ERROR:</span> '
            + evt.data);
      } 
	
      function doSend(message) {
         writeToScreen("SENT: " + message); websocket.send(message);
      }
	
      function writeToScreen(message) {
         var pre = document.createElement("p"); 
         pre.style.wordWrap = "break-word"; 
         pre.innerHTML = message; 
         output.appendChild(pre);
      }
	
      window.addEventListener("load", init, false);
   </script>
	
   <h2>WebSocket Test</h2>
   <div id = "output"></div>
	
</html>

输出如下:

WebSocket DISCONNECTED

WebSockets – 工作服务器

WebSocket 服务器是一个简单的程序,它能够处理 WebSocket 事件和操作。它通常向 WebSocket 客户端 API 公开类似的方法,并且大多数编程语言都提供了一种实现。下图说明了 WebSocket 服务器和 WebSocket 客户端之间的通信过程,重点说明了触发的事件和操作。

下图显示了 WebSocket 服务器和客户端事件触发:

Server Client

连接到 Web 服务器

WebSocket 服务器的工作方式类似于 WebSocket 客户端。它在必要时响应事件并执行操作。无论使用哪种编程语言,每个 WebSocket 服务器都会执行一些特定操作。

它初始化为一个 WebSocket 地址。它处理OnOpen、OnCloseOnMessage 事件,并向客户端发送消息。

创建 WebSocket 服务器实例

每个 WebSocket 服务器都需要一个有效的主机和端口。在服务器中创建 WebSocket 实例的示例如下所示:

var server = new WebSocketServer("ws://127.0.0.1:8181");

任何有效的 URL 都可以与先前未使用的端口规范一起使用。记录已连接的客户端非常有用,因为它提供具有不同数据或向每个客户端发送不同消息的详细信息。

Fleck 使用IwebSocketConnection 接口表示传入的连接(客户端)。每当有人连接到或断开我们的服务时,都可以创建或更新空列表。

var clients = new List<IWebSocketConnection>();

之后,我们可以调用Start 方法并等待客户端连接。启动后,服务器能够接受传入的连接。在 Fleck 中,Start 方法需要一个参数,该参数指示引发事件的套接字:

server.Start(socket) =>
{
});

OnOpen 事件

OnOpen 事件确定新的客户端已请求访问并执行初始握手。应将客户端添加到列表中,并且可能应存储与其相关的相关信息,例如 IP 地址。Fleck 为我们提供了此类信息,以及连接的唯一标识符。

server.Start(socket) ⇒ {

   socket.OnOpen = () ⇒ {
      // Add the incoming connection to our list.
      clients.Add(socket);
   }
	
   // Handle the other events here...
});

OnClose 事件

只要客户端断开连接,就会触发OnClose事件。客户端将从列表中移除,并通知其余客户端其断开连接。

socket.OnClose = () ⇒ {
   // Remove the disconnected client from the list.
   clients.Remove(socket);
};

OnMessage 事件

当客户端向服务器发送数据时,就会触发OnMessage事件。在这个事件处理程序中,可以将传入的消息传输到客户端,或者可能只选择其中一部分客户端。

流程很简单。请注意,此处理程序采用名为message的字符串作为参数:

socket.OnMessage = () ⇒ {
   // Display the message on the console.
   Console.WriteLine(message);
};

Send() 方法

Send()方法简单地将所需消息传输到指定的客户端。使用Send(),可以在客户端之间存储文本或二进制数据。

OnMessage事件的工作原理如下:

socket.OnMessage = () ⇒ {
   foreach (var client in clients) {
      // Send the message to everyone!
      // Also, send the client connection's unique identifier in order
      // to recognize who is who.
      client.Send(client.ConnectionInfo.Id + " says: " + message);
   }
};

WebSockets - API

API – 定义

API是应用程序编程接口(Application Program Interface)的缩写,是一套用于构建软件应用程序的例程、协议和工具。

一些重要的特性包括:

  • API指定了软件组件应该如何交互,并且在编程图形用户界面(GUI)组件时应该使用API。

  • 一个好的API通过提供所有构建块来简化程序的开发。

  • REST通常运行在HTTP之上,经常用于移动应用程序、社交网站、mashup工具和自动化业务流程。

  • REST风格强调通过有限数量的操作(动词)来增强客户端和服务之间的交互。

  • 通过为资源分配其自身的唯一通用资源标识符(URI)来提供灵活性。

  • REST避免歧义,因为每个动词都有特定的含义(GET、POST、PUT和DELETE)

WebSocket 的优势

WebSocket解决了一些REST或HTTP本身存在的问题:

双向通信

HTTP是一个单向协议,客户端总是发起请求。服务器处理并返回响应,然后客户端使用它。WebSocket是一个双向协议,没有预定义的消息模式,例如请求/响应。客户端或服务器都可以向另一方发送消息。

全双工

HTTP允许请求消息从客户端到服务器,然后服务器将响应消息发送回客户端。在给定时间,要么客户端与服务器通信,要么服务器与客户端通信。WebSocket允许客户端和服务器彼此独立地通信。

单个TCP连接

通常,为HTTP请求启动一个新的TCP连接,并在收到响应后终止。另一个HTTP请求/响应需要建立一个新的TCP连接。对于WebSocket,使用标准HTTP升级机制升级HTTP连接,并且客户端和服务器在WebSocket连接的生命周期内通过相同的TCP连接进行通信。

下图显示了处理N条具有恒定有效负载大小的消息所花费的时间(以毫秒为单位)。

Single Connection

以下是提供给该图的原始数据:

Constant Payload

上图和表显示,REST开销随着消息数量的增加而增加。这是因为需要启动和终止许多TCP连接,并且需要发送和接收许多HTTP标头。

最后一列特别显示了完成REST请求所需时间的乘法因子。

第二个图显示了通过改变有效负载大小来处理固定数量的消息所花费的时间。

Websockets Rest

以下是提供给该图的原始数据:

Constant Number

该图显示,处理REST端点请求/响应的增量成本最小,大部分时间都花在连接启动/终止和遵守HTTP语义上。

结论

WebSocket是一个低级协议。所有内容,包括简单的请求/响应设计模式、如何创建/更新/删除资源需求、状态码等,都需要在其之上构建。所有这些对于HTTP来说都是明确定义的。

WebSocket是一个有状态协议,而HTTP是一个无状态协议。WebSocket连接可以在单个服务器上垂直扩展,而HTTP可以水平扩展。WebSocket水平扩展有一些专有解决方案,但它们不是基于标准的。HTTP附带了许多其他好处,例如缓存、路由和多路复用。所有这些都需要在WebSocket之上定义。

WebSockets - JavaScript应用程序

下面的程序代码描述了使用JavaScript和WebSocket协议的聊天应用程序的工作原理。

<!DOCTYPE html>
<html lang = "en">

   <head>
      <meta charset = utf-8>
      <title>HTML5 Chat</title>
		
      <body>
		
         <section id = "wrapper">
			
            <header>
               <h1>HTML5 Chat</h1>
            </header>
				
            <style>
               #chat { width: 97%; }
               .message { font-weight: bold; }
               .message:before { content: ' '; color: #bbb; font-size: 14px; }
					
               #log {
                  overflow: auto;
                  max-height: 300px;
                  list-style: none;
                  padding: 0;
               }
					
               #log li {
                  border-top: 1px solid #ccc;
                  margin: 0;
                  padding: 10px 0;
               }
					
               body {
                  font: normal 16px/20px "Helvetica Neue", Helvetica, sans-serif;
                  background: rgb(237, 237, 236);
                  margin: 0;
                  margin-top: 40px;
                  padding: 0;
               }
					
               section, header {
                  display: block;
               }
					
               #wrapper {
                  width: 600px;
                  margin: 0 auto;
                  background: #fff;
                  border-radius: 10px;
                  border-top: 1px solid #fff;
                  padding-bottom: 16px;
               }
					
               h1 {
                  padding-top: 10px;
               }
					
               h2 {
                  font-size: 100%;
                  font-style: italic;
               }
					
               header, article > * {
                  margin: 20px;
               }
					
               #status {
                  padding: 5px;
                  color: #fff;
                  background: #ccc;
               }
					
               #status.fail {
                  background: #c00;
               }
					
               #status.success {
                  background: #0c0;
               }
					
               #status.offline {
                  background: #c00;
               }
					
               #status.online {
                  background: #0c0;
               }
					
               #html5badge {
                  margin-left: -30px;
                  border: 0;
               }
					
               #html5badge img {
                  border: 0;
               }
            </style>
				
            <article>
				
               <form onsubmit = "addMessage(); return false;">
                  <input type = "text" id = "chat" placeholder = "type and press 
                  enter to chat" />
               </form>
					
               <p id = "status">Not connected</p>
               <p>Users connected: <span id = "connected">0
                  </span></p>
               <ul id = "log"></ul>
					
            </article>
				
            <script>
               connected = document.getElementById("connected");
               log = document.getElementById("log");
               chat = document.getElementById("chat");
               form = chat.form;
               state = document.getElementById("status");
					
               if (window.WebSocket === undefined) {
                  state.innerHTML = "sockets not supported";
                  state.className = "fail";
               }else {
                  if (typeof String.prototype.startsWith != "function") {
                     String.prototype.startsWith = function (str) {
                        return this.indexOf(str) == 0;
                     };
                  }
						
                  window.addEventListener("load", onLoad, false);
               }
					
               function onLoad() {
                  var wsUri = "ws://127.0.0.1:7777";
                  websocket = new WebSocket(wsUri);
                  websocket.onopen = function(evt) { onOpen(evt) };
                  websocket.onclose = function(evt) { onClose(evt) };
                  websocket.onmessage = function(evt) { onMessage(evt) };
                  websocket.onerror = function(evt) { onError(evt) };
               }
					
               function onOpen(evt) {
                  state.className = "success";
                  state.innerHTML = "Connected to server";
               }
					
               function onClose(evt) {
                  state.className = "fail";
                  state.innerHTML = "Not connected";
                  connected.innerHTML = "0";
               }
					
               function onMessage(evt) {
                  // There are two types of messages:
                  // 1. a chat participant message itself
                  // 2. a message with a number of connected chat participants
                  var message = evt.data;
						
                  if (message.startsWith("log:")) {
                     message = message.slice("log:".length);
                     log.innerHTML = '<li class = "message">' + 
                        message + "</li>" + log.innerHTML;
                  }else if (message.startsWith("connected:")) {
                     message = message.slice("connected:".length);
                     connected.innerHTML = message;
                  }
               }
					
               function onError(evt) {
                  state.className = "fail";
                  state.innerHTML = "Communication error";
               }
					
               function addMessage() {
                  var message = chat.value;
                  chat.value = "";
                  websocket.send(message);
               }
					
            </script>
				
         </section>
			
      </body>
		
   </head>	
	
</html>

下面讨论了聊天应用程序的关键功能和输出:

要进行测试,请打开两个支持WebSocket的窗口,在上面键入消息并按回车键。这将启用聊天应用程序的功能。

如果未建立连接,则输出如下所示。

HTML5 Chat

成功的聊天通信输出如下所示。

Browser Support

WebSockets - 与服务器通信

Web在很大程度上是围绕HTTP的请求/响应范例构建的。客户端加载网页,然后在用户点击下一页之前不会发生任何事情。大约在2005年,AJAX开始使Web感觉更加动态。尽管如此,所有HTTP通信都由客户端控制,这需要用户交互或定期轮询才能从服务器加载新数据。

允许服务器在知道有新数据可用时立即将数据发送给客户端的技术已经存在了一段时间。它们被称为“推送”“Comet”

使用长轮询,客户端打开到服务器的HTTP连接,该连接保持打开状态直到发送响应。每当服务器实际拥有新数据时,它都会发送响应。长轮询和其他技术运行良好。然而,所有这些都存在一个共同的问题,它们带有HTTP的开销,这使得它们不适合低延迟应用程序。例如,浏览器中的多人射击游戏或任何其他具有实时组件的在线游戏。

将套接字引入Web

WebSocket规范定义了一个API,用于在Web浏览器和服务器之间建立“套接字”连接。简单来说,客户端和服务器之间存在持久连接,并且双方都可以随时开始发送数据。

可以使用构造函数简单地打开WebSocket连接:

var connection = new WebSocket('ws://html5rocks.websocket.org/echo', ['soap', 'xmpp']);

ws是WebSocket连接的新URL模式。还有wss,用于安全WebSocket连接,就像https用于安全HTTP连接一样。

立即向连接附加一些事件处理程序,可以让你知道连接何时打开、收到传入消息或发生错误。

第二个参数接受可选的子协议。它可以是一个字符串或一个字符串数组。每个字符串都应该表示一个子协议名称,服务器只接受数组中传递的子协议之一。可以通过访问WebSocket对象的protocol属性来确定已接受的子协议

// When the connection is open, send some data to the server
connection.onopen = function () {
   connection.send('Ping'); // Send the message 'Ping' to the server
};

// Log errors
connection.onerror = function (error) {
   console.log('WebSocket Error ' + error);
};

// Log messages from the server
connection.onmessage = function (e) {
   console.log('Server: ' + e.data);
};

与服务器通信

一旦我们与服务器建立连接(当打开事件触发时),我们就可以开始使用连接对象上的send(your message)方法向服务器发送数据。它过去只支持字符串,但在最新的规范中,它现在也可以发送二进制消息了。要发送二进制数据,可以使用Blob或ArrayBuffer对象。

// Sending String
connection.send('your message');

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);

for (var i = 0; i < img.data.length; i++) {
   binary[i] = img.data[i];
}

connection.send(binary.buffer);

// Sending file as Blob
var file = document.querySelector('input[type = "file"]').files[0];
connection.send(file);

同样,服务器也可能随时向我们发送消息。每当发生这种情况时,onmessage回调就会触发。回调接收一个事件对象,实际消息可以通过data属性访问。

在最新的规范中,WebSocket也可以接收二进制消息。二进制帧可以以Blob或ArrayBuffer格式接收。要指定接收到的二进制的格式,请将WebSocket对象的binaryType属性设置为'blob'或'arraybuffer'。默认格式为'blob'。

// Setting binaryType to accept received binary as either 'blob' or 'arraybuffer'
connection.binaryType = 'arraybuffer';
connection.onmessage = function(e) {
   console.log(e.data.byteLength); // ArrayBuffer object if binary
};

WebSocket的另一个新增功能是扩展。使用扩展,可以发送压缩、多路复用等帧。

// Determining accepted extensions
console.log(connection.extensions);

跨域通信

作为一个现代协议,跨域通信直接集成到WebSocket中。WebSocket允许在任何域上的各方之间进行通信。服务器决定是将其服务提供给所有客户端,还是只提供给位于一组明确定义的域中的客户端。

代理服务器

每项新技术都会带来一组新的问题。对于WebSocket来说,它与代理服务器的兼容性就是一个问题,代理服务器在大多数公司网络中都会协调HTTP连接。WebSocket协议使用HTTP升级系统(通常用于HTTP/SSL)将HTTP连接“升级”到WebSocket连接。一些代理服务器不喜欢这样做,并将断开连接。因此,即使给定的客户端使用WebSocket协议,也可能无法建立连接。这使得下一节更加重要 :)

服务器端

使用WebSocket为服务器端应用程序创建了一种全新的使用模式。虽然LAMP等传统的服务器堆栈是围绕HTTP请求/响应周期设计的,但它们通常无法很好地处理大量的打开的WebSocket连接。同时保持大量连接打开需要一种能够以低性能成本接收高并发性的架构。

WebSockets - 安全性

出于安全原因,应设计协议。WebSocket是一个全新的协议,并非所有Web浏览器都正确实现了它。例如,其中一些仍然允许混合使用HTTP和WS,尽管规范暗示相反。在本章中,我们将讨论用户应该了解的一些常见安全攻击。

拒绝服务

拒绝服务(DoS)攻击试图使机器或网络资源对请求它的用户不可用。假设有人以无时间间隔或极短的时间间隔向Web服务器发出无限数量的请求。服务器无法处理每个连接,要么停止响应,要么响应速度太慢。这可以称为拒绝服务攻击。

拒绝服务对于最终用户来说非常令人沮丧,他们甚至无法加载网页。

DoS攻击甚至可以应用于对等通信,迫使P2P网络的客户端同时连接到受害者的Web服务器。

中间人攻击

让我们通过一个例子来理解这一点。

假设一个人A通过IM客户端与他的朋友B聊天。一些第三者想要查看你们交换的消息。因此,他与这两个人建立了独立的连接。他还作为你们通信的隐形中间人向A和他朋友B发送消息。这被称为中间人攻击。

对于未加密的连接,中间人类型的攻击更容易进行,因为入侵者可以直接读取数据包。当连接被加密时,信息必须由攻击者解密,这可能非常困难。

从技术角度来看,攻击者拦截公开密钥消息交换并发送消息,同时将其请求的密钥替换为自己的密钥。显然,使攻击者难以工作的可靠策略是将SSH与WebSockets一起使用。

在交换关键数据时,大多数情况下,优先使用安全的WSS连接而不是未加密的WS。

XSS

跨站脚本(XSS)是一种漏洞,允许攻击者将客户端脚本注入网页或应用程序。攻击者可以使用你的应用程序中心发送HTML或Javascript代码,并让此代码在客户端机器上执行。

WebSocket 本地防御机制

默认情况下,WebSocket协议的设计是安全的。在现实世界中,用户可能会遇到由于浏览器实现不佳而可能出现的一些问题。随着时间的推移,浏览器厂商会立即修复任何问题。

当使用通过SSH(或TLS)的安全WebSocket连接时,会添加额外的安全层。

在WebSocket世界中,主要关注的是安全连接的性能。尽管顶部仍然存在额外的TLS层,但协议本身包含对此类使用的优化,此外,WSS通过代理运行得更流畅。

客户端到服务器的掩码

在 WebSocket 服务器和 WebSocket 客户端之间传输的每条消息都包含一个特定的密钥,称为掩码密钥,它允许任何符合 WebSocket 标准的中介能够解掩码并检查消息。如果中介不符合 WebSocket 标准,则无法影响消息。实现 WebSocket 协议的浏览器负责处理掩码。

安全工具箱

最后,可以提供一些有用的工具来调查 WebSocket 客户端和服务器之间信息流,分析交换的数据,并识别潜在的风险。

浏览器开发者工具

Chrome、Firefox 和 Opera 在开发者支持方面都是优秀的浏览器。它们内置的工具可以帮助我们确定客户端交互和资源的几乎任何方面。这对安全至关重要。

WebSockets - 移动应用

顾名思义,WebSocket 使用的是网络。网络通常与浏览器页面紧密相连,因为它们是在线显示数据的主要手段。但是,非浏览器程序也使用在线数据传输。

iPhone(最初)和iPad(之后)的发布,引入了无需使用网络浏览器即可实现网络互连的全新世界。取而代之的是,新型智能手机和平板电脑利用原生应用程序的功能,提供独特的用户体验。

移动端的重要性

目前,全球有数十亿部活跃的智能手机。也就是说,您的应用程序有数百万潜在客户。这些人使用他们的手机来完成日常任务、浏览互联网、沟通或购物。

智能手机已成为应用程序的代名词。如今,任何用户可以想到的用途都有相应的应用程序。大多数应用程序连接到互联网以检索数据、进行交易、收集新闻等等。

最好是利用现有的 WebSocket 知识,开发一个在智能手机或平板电脑上原生运行的 WebSocket 客户端。

原生移动应用与移动网站

这是一个常见的冲突,答案通常取决于目标用户的需求。如果用户熟悉现代设计趋势,那么设计一个响应式且对移动设备友好的网站是必须的。但是,最终用户必须确保内容(这才是真正重要的)通过智能手机访问与通过经典桌面浏览器访问一样容易。

毫无疑问,WebSocket 网络应用程序可以在任何兼容 HTML5 的浏览器上运行,包括移动浏览器,例如 iOS 版 Safari 和移动版 Chrome。因此,无需担心与智能手机的兼容性问题。

先决条件

为了开发智能手机应用程序,需要安装开发工具和 SDK。

smartphone

WebSockets 可以充当在连接的移动和平板客户端之间传输消息的通用中心。我们可以实现一个原生 iOS 应用程序,它与 WebSocket 服务器的通信方式就像 HTML5 JavaScript 客户端一样。

广告