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和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 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 简单对象访问协议)或自定义协议。
如果URL有语法错误,构造函数将抛出异常。在第一次WebSocket连接握手,客户发送带有协议名称的Sec-WebSocket-Protocol首标。服务器选择0个或1个协议,响应一个带有和客户请求相同的协议名称的Sec-WebSocket-Protocol首标;否则,服务器关闭连接。
协议协商对于确定WebSocket服务器支持的协议及版本很有用,应用程序可能支持多个协议,使用协议协商选择与特定服务器通信的协议。WebSocket构造函数还可以包含一组客户端支持的协议,让服务器决定使用其中的一个。
由于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。
到open事件触发时,协议握手已经完成,WebSocket已经准备好发送和接收数据。如果应用程序接收到一个open事件,那么可以确定WebSocket服务器成功地处理了连接请求,并且同意与应用程序通信。
message:WebSocket消息包含来自服务器的数据。WebSocket API只输出完整的消息,而不是WebSocket帧。message事件在接收到消息时触发,对应于该事件的回调函数是onmessage。
除了文本,WebSocket消息还可以处理二进制数据,这种数据作为Blob消息或者ArrayBuffer消息处理。因为设置WebSocket消息二进制数据类型的应用程序会影响二进制消息,所以必须在读取数据之前决定用于客户端二进制输入数据的类型。
error:error事件在响应意外故障时触发。与该事件对应的回调函数为onerror。错误还会导致WebSocket连接关闭。如果你接收一个error事件,可以预期很快就会触发close事件。close事件中的代码和原因有时候能告诉你错误的根源。error事件处理程序是调用服务器重连逻辑以及处理来自WebSocket对象的异常的最佳场所。
close:close事件在WebSocket连接关闭时触发。对应于close事件的回调函数是onclose。一旦连接关闭,客户端和服务器不再能接收或者发送消息。当调用close()方法终止与服务器的连接时,也会触发onclose事件处理程序。
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()方法在连接打开的时候发送数据。如果连接不可用或者关闭,它抛出一个有关无效连接状态的异常。如果想发送消息响应另一个事件,可以检查WebSocket readyState属性,并选择只在套接字打开时发送数据。
除了文本(字符串)消息之外,WebSocket API允许发送二进制数据,这对实现二进制协议特别有用。这样的二进制协议可能是TCP上层的标准互联网协议,这些协议的载荷可能是Blob或ArrayBuffer。Blob对象在与JavaScript File API结合使用以发送和接收文件时特别有用,这些文件主要有多媒体文件、图视频和音频。
close():使用close()方法,可以关闭WebSocket连接或者终止连接尝试。如果连接已经关闭,该方法就什么都不做。在调用close()之后,不能在已经关闭的WebSocket上发送任何数据。可以向close(0方法传递两个可选参数:code(数字型的状态代码)和reason(一个文本字符串)。传递这些参数能够向服务器传递关于客户关闭连接原因的信息。
举例
创建客户端应用程序,通过Web与一台远程服务器通信,用WebSocket交换数据。JavaScript客户端示例使用ws://echo.websocket.org上的回显服务器,它可以接收并返回发送到服务器的任何消息。
WebSocket对象特性
WebSocket对象通过只读特性readyState报告其连接状态,这个属性根据连接状态自动变化。正如WebSocket API所描述的,当WebSocket对象第一次创建时,readyState为0,表示套接字正在连接。
设计应用程序时,可能想要检查发往服务器的缓冲数据量,特别是在客户端应用程序向服务器发送大量数据的时候。尽管调用send()是立即生效的,但是数据在互联网上的传输却不是如此。浏览器将为你的客户端应用程序缓存出站数据,从而使你可以随时调用send(),发送任意数量的数据。然而,如果你想知道数据在网络上传送的速率,WebSocket对象可以告诉你缓存的大小。你可以使用bufferedAmount特性检查已经进入队列,但是尚未发送到服务器的字节数。这个特性报告的值不包括协议组帧开销或者操作系统、网络硬件所进行的缓冲。
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。
在WebSocket中使用HTML5媒体
作为HTML5和Web平台的一部分,WebSocket API可以很好地和所有HTML5特性配合。这个API所能发送和接收的数据类型广泛地用于传输应用程序数据和媒体。字符串当然可以表示XML和JSON等Web数据格式。二进制类型可以和拖放(Drag-and-Drop)、FileReader、WebGL和Web Audio API等集成。
将图像拖放到页面上,代码会将图像发送给websocket回显服务器,完成拖放后,应该可以看到图像显示在网页上。websocket.org服务器目前只接受小消息,所以这个例子只适用于小于65KB的图像文件。
使用C++实现WebSocket Server
WebSocket Server C++代码实现:
点击connect按钮,服务器代码显示
点击send按钮,浏览器发送数据,服务器回送数据给浏览器。
var wsServer = 'ws://192.168.189.155:9096/';
ws代表websocket的意思。
使用Node.js编写实现WebSocket Server
http://www.websocket.org/
http://webplatform.org
http://html5rocks.com