WebSocket协议入门:WebSocket API
  5b99XfAwWKiH 2023年11月02日 66 0


HTML5连接性领域包括WebSocket、服务器发送事件和跨文档消息传递(Cross-Document Messaging)等技术。在HTML5之前,浏览器窗口和框架之间的通信由于安全的原因而受到限制。然而,随着Web应用程序开始组合不同网站中的内容和应用程序,这些应用程序的相互通信变得必不可少。为了解决这个问题,标准组织和主要浏览器供应商同意支持跨文档消息传递,后者能够确保在浏览器窗口、选项卡和iFrame之间跨源通信的安全。跨文档消息传递定义了postMessage API,作为发送和接收消息的标准手段,其也提供了不同JavaScript上下文之间的异步消息传递。跨文档消息传递通过允许消息在不同源之间交换,克服了同源策略的限制。当你发送消息时,发送者指定接收者的源;当你接收消息时,发送者的源会被作为消息的一部分。消息的源由浏览器提供,不会被伪造。
跨文档消息传递是HTML5规范利用强大的API简化Web应用程序之间通信的绝佳范例。但是,它的重点被限制在跨窗口、选项卡和iFrame通信上。它不能解决协议通信中正在变得越来越严重的复杂性,这时就要求助WebSocket。
HTML5规范的主要编者Ian Hickson在HTML5规范的通信部分中添加了WebSocket。最初称为TCPConnection,现已发展为一个独立的规范。虽然WebSocket目前存在于HTML5的领域之外,但是它对于实现现代Web应用程序中的实时连接性至关重要。

旧的HTTP架构

在旧的架构中,连接性由HTTP/1.0和HTTP/1.1处理。HTTP是客户端/服务器模式中请求-响应所用的协议。HTTP/1.0对于从服务器请求单个文档来说已经足够了,但是,随着Web的成长超出了简单的文档共享,并开始包含更多的交互性,连接性需要进行微调,以缩短浏览器请求和服务器响应之间的时间。在HTTP/1.0中,每个服务器请求需要一个单独的连接,HTTP/1.1增加了可重用连接。浏览器可以初始化一个到Web服务器的连接,以读取HTML页面,然后重用该连接读取图片、脚本等资源。
HTTP是无状态的,它将每个请求当成唯一和独立的。服务器不需要保存有关会话的信息,从而不需要存储数据。但是,这也意味着在每次HTTP请求和响应中都会发送关于请求的冗余信息。
从根本上讲,HTTP还是半双工的协议,也就是说,在同一时刻流量只能单向流动:客户端向服务器发送请求(单向),然后服务器响应请求(单向)。半双工的效率很低。

HTTP轮询、长轮询和流化

浏览器访问网页时,会向页面所在的服务器发送一个HTTP请求。Web服务器确认请求并向浏览器发回响应。在许多情况下,返回的信息(如股价、新闻、交通图、医疗设备读数和天气信息)到达浏览器显示页面时已经过时。如果用户需要得到最新的实时信息,可以不断刷新页面,但是这并不实际,也不是特别精妙的解决方案。
当前对提供实时Web应用程序的尝试多半围绕轮询(polling)技术进行,这种技术模拟其他服务器端“推”技术(最流行的是Comet),本质上就是推迟完成HTTP响应,向客户端提交信息
轮询是一种定时的同步调用,客户端向服务器发送请求查看是否有可用的新信息。请求以固定的时间间隔发出,不管是否有信息,客户端都会得到响应:如果有可用信息,服务器发送这些信息;如果没有可用信息,服务器返回一个拒绝响应,客户端关闭连接。如果你知道信息交付的精确间隔,轮询是一个好的解决方案,因为你可以同步客户端,只在你知道服务器上有可用信息的时候发送请求、因而打开过多连接是不可避免的。结果是,在低信息率的情况下,你可能打开或者关闭许多不必要的连接。
长轮询(long polling)是另一种流行的通信方法,客户端向服务器请求信息,并在设定的时间段内打开一个连接。服务器如果没有任何信息,会保持请求打开,直到有客户端可用的信息,或者直到指定的超时时间用完为止。这时,客户端重新向服务器请求信息。长轮询也称作Comet或者反向AJAX。Comet延长HTTP响应的完成,直到服务器有需要发送给客户端的内容,这种技术常常称为“挂起GET”或“搁置POST”。重要的是要知道,当信息量很大时,长轮询相对于传统轮询并没有明显的性能优势,因为客户端必须频繁地重连到服务器以读取新信息,造成网络的表现和快速轮询相同。长轮询的另一个问题是缺乏标准实现。
在流化技术中,客户端发送一个请求,服务器发送并维护一个持续更新和保持打开(可以是无限或者规定的时间段)的开放响应。每当服务器有需要交付给客户端的信息时,它就更新响应。看起来,流化是能够适应不可预测的信息交付的极佳方案,但是服务器从不发出完成HTTP响应的请求,从而使连接一直保持打开。在这种情况下,代理和防护墙可能缓存响应,导致信息交付的延迟增加。因此,许多流化的尝试对于存在防火墙和代理的网络是不友好的。

WebSocket

WebSocket是一种自然的全双工、双向、单套接字连接。使用WebSocket,你的HTTP请求变成打开WebSocket连接(WebSocket或者WebSocket over TLS (Transport Layer Security,传输层安全性,原称SSL))的单一请求,并且重用从客户端到服务器以及服务器到客户端的同一连接。WebSocket减少了延迟,因为一旦建立起WebSocket连接,服务器可以在消息可用时发送它们。例如,和轮询不同,WebSocket只发出一个请求。服务器不需要等待来自客户端的请求。相似地,客户端可以在任何时候向服务器发送消息。相比轮询不管是否有可用消息,每隔一段时间都发送一个请求,单一请求大大减少了延迟。

WebSocket协议入门:WebSocket API_websocket

WebSocket和RFC 6455

WebSocket是一个协议,但是还有一个WebSocket API,应用程序可以用它控制WebSOcket协议,响应服务器触发的事件。这个API由W3C开发,协议是由IETF开发。WebSocket API现在得到现代浏览器的支持,包含了使用全双工、双向WebSocket连接所需的方法和特性。利用这个API,可以执行必要的操作,例如打开和关闭连接、发送和接收消息、监听服务器触发的事件。WebSocket协议能够通过Web进行客户端和远程服务器之间的全双工通信,并支持二进制数据和文本字符串的传输。

WebSocket服务器的实现多种多样,例如Apache mod_pywebsocket、Jetty、Socket.IO和Kaazing的WebSocket Gateway。

WebSocket协议入门:WebSocket API_应用程序_02

WebSocket API

WebSocket构造函数

调用WebSocket构造函数通过指向一个代表所要连接端点的URL来创建一个WebSocket连接,构造函数返回WebSocket对象实例。可以监听该对象上的事件,这些事件告诉你何时连接打开,何时消息到达,何时连接关闭以及何时发生错误。你也可以与WebSocket对象交互,发送消息或者关闭连接。
WebSocket协议定义了两种URL方案(URL scheme)-ws和wss,分别用于客户端和服务器之间的非加密与加密流量。ws(WebSocket)方案与HTTP URI方案类似,wss(WebSocket Secure)URI方案表示使用传输层安全性(TLS)的WebSocket连接,使用HTTPS采用的安全机制来保证HTTP连接的安全。构造函数还有一个可选参数protocols(为了建立连接,服务器必须在其响应中包含的一个或一组协议名称)。在protocols参数中可以使用的协议包括XMPP(eXtensible Messaging and Presence Protocol 可扩展消息处理现场协议)、SOAP(Simple Object Access Protocol 简单对象访问协议)或自定义协议。

// Create new WebSocket connection
var ws = new WebSocket("ws://www.websocket.org");

如果URL有语法错误,构造函数将抛出异常。在第一次WebSocket连接握手,客户发送带有协议名称的Sec-WebSocket-Protocol首标。服务器选择0个或1个协议,响应一个带有和客户请求相同的协议名称的Sec-WebSocket-Protocol首标;否则,服务器关闭连接。
协议协商对于确定WebSocket服务器支持的协议及版本很有用,应用程序可能支持多个协议,使用协议协商选择与特定服务器通信的协议。WebSocket构造函数还可以包含一组客户端支持的协议,让服务器决定使用其中的一个。

// Connecting to the server with multiple protocol choices
var echoSocket = new WebSocket("ws://echo.websocket.org",["com.kaazing.echo","example.imaginary.protocol"]);

echoSocket.onopen = function(e) {
// Check the protocol chosen by the server
console.log(echoSocket.protocol);
};

由于WebSocket服务器ws://echo.websocket.org只理解com.kaazing.echo协议,而不理解example.imaginary.protocol,该服务器在触发WebSocket open事件的时候选择com.kaazing.echo协议。使用数组提供了让应用程序对不同服务器使用不同协议的灵活性。

WebSocket事件

WebSocket API是纯事件驱动的,应用程序代码监听WebSocket对象上的事件,以便处理输入数据和连接状态的改变。WebSocket协议也是事件驱动的,客户端应用程序不需要轮询服务器来得到更新的数据。消息和事件将在服务器发送它们的时候异步到达。要开始监听事件,只要为WebSocket对象添加回调函数,也可以使用addEventListener() DOM方法为WebSocket对象添加事件监听器

WebSocket对象调度4个不同的事件:open、message、error、close
和所有Web API一样,可以用on<事件名称>处理程序属性监听这些事件,也可以使用addEventListener()方法。
open:一旦服务器响应了WebSocket连接请求,open事件触发并建立一个连接。open事件对应的回调函数称作onopen。

// Event handler for the WebSocket connection opening
ws.open = functon(e) {
console.log("Connection open...");
};

到open事件触发时,协议握手已经完成,WebSocket已经准备好发送和接收数据。如果应用程序接收到一个open事件,那么可以确定WebSocket服务器成功地处理了连接请求,并且同意与应用程序通信。
message:WebSocket消息包含来自服务器的数据。WebSocket API只输出完整的消息,而不是WebSocket帧。message事件在接收到消息时触发,对应于该事件的回调函数是onmessage。

// Event handler for receiving text messages
ws.onmessage = function(e) {
if(typeof e.data == "string"){
console.log("String message received", e, e.data);
} else {
console.log("Other message received", e, e.data);
}
};

除了文本,WebSocket消息还可以处理二进制数据,这种数据作为Blob消息或者ArrayBuffer消息处理。因为设置WebSocket消息二进制数据类型的应用程序会影响二进制消息,所以必须在读取数据之前决定用于客户端二进制输入数据的类型。

// Set binaryType to blob (Bolb is the default.)
ws.binaryType = "blob";
// Event handler for receiving Blob messages
ws.onmessage = function(e) {
if(e.data instanceof Blon){
console.log("Blob message received", e.data);
var blob = new Blob(e.data);
}
};
// Set binaryType to ArrayBuffer messages
ws.binaryType = "arraybuffer";
// Event handler for receiving ArrayBuffer messages
ws.onmessage = function(e) {
if(e.data instanceof ArrayBuffer){
console.log("ArrayBuffer Message Received", + e.data);
// e.data is an ArrayBuffer. Create a byte view of that object.
var a = new Uint8Array(e.data);
}
}

error:error事件在响应意外故障时触发。与该事件对应的回调函数为onerror。错误还会导致WebSocket连接关闭。如果你接收一个error事件,可以预期很快就会触发close事件。close事件中的代码和原因有时候能告诉你错误的根源。error事件处理程序是调用服务器重连逻辑以及处理来自WebSocket对象的异常的最佳场所。

// Event handler for errors in the WebSocket object
ws.onerror = function(e) {
console.log("WebSocket Error: ", e);
// Custom function for handling errors
handleErrors(e);
};

close:close事件在WebSocket连接关闭时触发。对应于close事件的回调函数是onclose。一旦连接关闭,客户端和服务器不再能接收或者发送消息。当调用close()方法终止与服务器的连接时,也会触发onclose事件处理程序。

// Event handler for closed connections
ws.onclose = function(e) {
console.log("Connection closed", e);
};

WebSocket close事件在连接关闭时触发,这可能有多种原因,比如连接失败或成功的WebSocket关闭握手。WebSocket对象特性readyState反映了连接的状态(2为正在关闭,3为已关闭)。close事件有3个有用的属性,可以用于错误处理和恢复:wasClean、Code和error。wasClean属性是一个布尔属性,表示连接是否顺利关闭。如果WebSocket的关闭是对来自服务器的一个close帧的响应,则该属性为true。如果连接是因为其他原因(例如因为底层TCP连接关闭)关闭,则该属性为fasle。code和reason属性表示服务器发送的关闭握手状态。这些属性和WebSocket.close(0方法中的code和reason参数一致。

WebSocket对象有两个方法:send()和close()。
send:使用WebSocket在客户端和服务器之间建立全双工双向连接后,就可以在连接打开时(也就是在调用onopen监听器之后,调用onclose监听器之前)调用send()方法。使用send()方法可以从客户端向服务器发送消息。在发送一条或者多条消息之后,可以保持连接打开,或者调用close()方法终止连接。

// Send a text message
ws.send("Hello WebSocket!");

send()方法在连接打开的时候发送数据。如果连接不可用或者关闭,它抛出一个有关无效连接状态的异常。如果想发送消息响应另一个事件,可以检查WebSocket readyState属性,并选择只在套接字打开时发送数据。

// Handle outging data. Send on a WebSocket if that socket is open
funciton myEventHandler(data) {
if(ws.readyState === WebSocket.OPEN){
// The socket is open, so it is ok to send the data.
ws.send(data);
} else {
// Do something else in this case, Possibly ignore the data or enqueue it.
}
}

除了文本(字符串)消息之外,WebSocket API允许发送二进制数据,这对实现二进制协议特别有用。这样的二进制协议可能是TCP上层的标准互联网协议,这些协议的载荷可能是Blob或ArrayBuffer。Blob对象在与JavaScript File API结合使用以发送和接收文件时特别有用,这些文件主要有多媒体文件、图视频和音频。

// Send a Blob
var blob = new Blob("blob contents");
ws.send(blob);
// Send an ArrayBuffer
var a = new Uint8Array([8,6,7,5,3,0,9]);
ws.send(a.buffer);

close():使用close()方法,可以关闭WebSocket连接或者终止连接尝试。如果连接已经关闭,该方法就什么都不做。在调用close()之后,不能在已经关闭的WebSocket上发送任何数据。可以向close(0方法传递两个可选参数:code(数字型的状态代码)和reason(一个文本字符串)。传递这些参数能够向服务器传递关于客户关闭连接原因的信息。

// Close the WebSocket connection because the session has ended successfully
ws.close(1000, "Closing normally");

举例

创建客户端应用程序,通过Web与一台远程服务器通信,用WebSocket交换数据。JavaScript客户端示例使用ws://echo.websocket.org上的回显服务器,它可以接收并返回发送到服务器的任何消息。

<!DOCTYPE html>
<html>
<head>
<title>WebSocket Echo Client</title>
</head>
<body>
<h2>Websocket Echo Client</h2>
<div id="output"></div>
<script>
// Initialize WebSocket connection and event handlers
function setup() {
output = window.document.getElementById("output");
ws = new WebSocket("ws://echo.websocket.org/echo");
//Listen for the connection open event then call the sendMessage function
ws.onopen = function(e) {
log("Connected");
sendMessage("Hello WebSocket!");
}
//Listen for the close connection event
ws.onclose = function(e) {
log("Disconnected: " + e.reason);
}
//Listen for connection errors
ws.onerror = function(e) {
log("Error ");
}
//Listen for new messages arriving at the client
ws.onmessage = function(e) {
log("Message received: " + e.data);
// Close the socket once message has arrived
ws.close();
}
}
// Send a message on the WebSocket
function sendMessage(msg) {
ws.send(msg);
log("Message sent");
}
// Display logging ingormation in the document
function log(s) {
var p = window.document.createElement("p");
p.style.wordWrap = "break-word";
p.textContent = s;
output.appendChild(p);
//Also log information on the javascript console
console.log(s);
}

// Start runing
setup();
</script>
</body>
</html>

WebSocket协议入门:WebSocket API_应用程序_03

WebSocket对象特性

WebSocket对象通过只读特性readyState报告其连接状态,这个属性根据连接状态自动变化。正如WebSocket API所描述的,当WebSocket对象第一次创建时,readyState为0,表示套接字正在连接。

WebSocket协议入门:WebSocket API_应用程序_04


设计应用程序时,可能想要检查发往服务器的缓冲数据量,特别是在客户端应用程序向服务器发送大量数据的时候。尽管调用send()是立即生效的,但是数据在互联网上的传输却不是如此。浏览器将为你的客户端应用程序缓存出站数据,从而使你可以随时调用send(),发送任意数量的数据。然而,如果你想知道数据在网络上传送的速率,WebSocket对象可以告诉你缓存的大小。你可以使用bufferedAmount特性检查已经进入队列,但是尚未发送到服务器的字节数。这个特性报告的值不包括协议组帧开销或者操作系统、网络硬件所进行的缓冲。

// 10k max buffer size.
var THRESHOLD = 10240;
// Create a New WebSocket connection
var ws = new WebSocket("ws://echo.websocket.org/updates");
// Listen for the opening event
ws.onopen = function() {
// Attempt to send update every second.
setInterval(function(){
//Send only if the buffer is not full
if (ws.bufferedAmount < THRESHOLD){
ws.send(getApplicationState());
}
}, 1000);
};

protocol特性包含在打开握手期间WebSocket上使用的协议。protocol特性在最初的握手完成之前为空,如果服务器没有选择客户端提供的某个协议,该特性保持空值。

检查WebSocket支持

并非所有浏览器都原生支持WebSocket,确定浏览器是否支持WebSocket有多种方法。
打开浏览器的交互式JavaScript控制台,并求取window.WebSocket表达式的值。如果你看到WebSocket构造函数对象,就意味着浏览器对WebSocket有原生支持。如果返回空白或者未定义,浏览器对WebSocket就没有原生支持。
为了确保WebSocket应用程序在不支持WebSocket的浏览器中正常工作,可以自己编写(很复杂),使用填充插件(Polyfill,用于旧的浏览器、复制标准API的一个JavaScript程序库),或者使用Kaazing等WebSocket供应商,这些供应商支持WebSocket模拟,可以使任何浏览器(包括和Microsoft Internet Explorer 6同时代的)支持HTML5 WebSocket标准API。

if (window.WebSocket) {
console.log("This browser supports WebSocket!");
} else {
console.log("This browser does not support WebSocket!");
}

在WebSocket中使用HTML5媒体

作为HTML5和Web平台的一部分,WebSocket API可以很好地和所有HTML5特性配合。这个API所能发送和接收的数据类型广泛地用于传输应用程序数据和媒体。字符串当然可以表示XML和JSON等Web数据格式。二进制类型可以和拖放(Drag-and-Drop)、FileReader、WebGL和Web Audio API等集成。

WebSocket协议入门:WebSocket API_客户端_05

<!DOCTYPE html>
<html>
<head>
<title>WebSocket Image Drop</title>
</head>
<body>
<h1>Drop Image Here</h1>

<script>
//Initialize WebSocket connection
var wsUrl = "ws://echo.websocket.org/echo";
var ws = new WebSocket(wsUrl);
ws.onopen = function() {
console.log("open");
}
//Handle binary image data received on the WebSocket
ws.onmessage = function(e) {
var blob = e.data;
console.log("message: " + blob.size + "bytes");
//Work with prefixed URL API
if(window.webkitURL){
URL = webkitURL;
}
var uri = URL.createObjectURL(blob);
var img = document.createElement("img");
img.src = uri;
document.body.appendChild(img);
}
//Handle drop event
document.ondrop = function(e) {
document.body.style.backgroundColor = "#fff";
try {
e.preventDefault();
handleFileDrop(e.dataTransfer.files[0]);
return false;
} catch(err) {
console.log(err);
}
}
//Provide visual feedback for the drop area
document.ondragover = function(e) {
e.preventDefault();
document.body.style.backgroundColor = "#6fff41";
}
document.ondragleave = function() {
document.body.style.backgroundColor = "#fff";
}
//Read binary file contents and send them over WebSocket
function handleFileDrop(file) {
var reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function() {
console.log("sending: " + file.name);
ws.send(reader.result);
console.log("sending end");
}
}
</script>
</body>
</html>

将图像拖放到页面上,代码会将图像发送给websocket回显服务器,完成拖放后,应该可以看到图像显示在网页上。websocket.org服务器目前只接受小消息,所以这个例子只适用于小于65KB的图像文件。

使用C++实现WebSocket Server

WebSocket Server C++代码实现:

点击connect按钮,服务器代码显示

WebSocket协议入门:WebSocket API_websocket_06


WebSocket协议入门:WebSocket API_客户端_07


点击send按钮,浏览器发送数据,服务器回送数据给浏览器。

WebSocket协议入门:WebSocket API_服务器_08

WebSocket协议入门:WebSocket API_websocket_09


​var wsServer = 'ws://192.168.189.155:9096/';​​ws代表websocket的意思。

WebSocket协议入门:WebSocket API_websocket_10

WebSocket协议入门:WebSocket API_websocket_11

使用Node.js编写实现WebSocket Server

http://www.websocket.org/
http://webplatform.org
http://html5rocks.com


【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

推荐阅读
  ehrZuhofWJiC   2024年05月17日   39   0   0 服务器linux
5b99XfAwWKiH