WebSocket是HTML5提供的一种在单个TCP连接上进行全双工通信(双向通信)的协议,WebSocket使客户端和服务端之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。
Web中已经有了HTTP协议,为什么还需要WebSocket协议呢?
由于HTTP协议有一个天生的缺陷:通信只能由客户端发起,因此HTTP协议做不到服务器主动向客户端推送信息。
HTTP这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就必须采用轮询的方式。而轮询的效率低下且非常浪费资源,因为必须不停地连接或者让HTTP连接始终打开。此时,也就出现了WebSocket。
WebSocket协议诞生于2008年,2011年成为国际标准,主流浏览器均已经得到支持。WebSocket协议最大的特点是服务器可以主动的向客户端推送信息,客户端也可以主动的向服务器发送信息,它是真正的双向平等对话,属于服务器推送技术的一种。
WebSocket协议的特点
- WebSocket协议建立在TCP协议之上,服务器实现比较容易。
- WebSocket协议与HTTP协议有着良好的兼容性,默认端口是80和443,握手阶段采用HTTP协议因此不容易屏蔽,能通过各种HTTP代理服务器。
- WebSocket数据格式比较轻量,性能开销较小,通信高效。
- WebSocket可以发送文本也可以发送二进制数据
- WebSocket没有同源限制,客户端可以与任意服务器通信。
- WebSocket协议标识符是
ws
或wss
,服务器地址直接是URL。
WebSocket API
WebSocket包含网络协议与API,让用户能够在客户端和服务端创建WebSocket连接。WebSocketAPI是使用WebSocket协议的接口,通过它来创建全双工通道以收发消息。
WebSocketAPI是纯事件驱动,一旦建立全双工链接,当服务端给客户端发送数据或资源时能自动发送状态改变的数据和通知。所以不需要为了状态的更新而去轮询服务器,只需要在客户端监听即可。
在WebSocket的API中,客户端和服务端只需要完成一次握手动作,然后客户端与服务端之间就会形成一条快速通道,两者之间就可以直接创建持久性的连接,并进行双向数据传输。
客户端通过JavaScript向服务端发起建立WebSocket连接的握手请求,当连接建立成功以后,客户端和服务端就可以通过TCP连接直接交换数据,当获取WebSocket连接后,可通过send()
方法来向服务端发送数据,客户端通过onmessage
事件来接收服务端返回的数据。
WebSocket协议本质
WebSocket协议本质上是一个基于TCP的协议 ,为了建立一个WebSocket连接,客户端浏览器首先要向服务器发起一个HTTP握手请求,这个请求和通常的HTTP请求有所不同,它包含了一些附加的头信息,其中会附加头信息Upgrade: WebSocket
表明这是一个申请协议升级的HTTP请求。服务器解析这些附加的头信息后会产生应答信息并返回给客户端,客户端和服务器的WebSocket连接就会建立起来,双方就可以通过连接通道自由的传递信息,并且这个连接会持续存在直到客户端或服务器的某一方主动关闭连接。
客户端发送的典型WebSocket握手请求
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
服务器回应的WebSocket典型握手响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/
WebSocket专有字段分析
Upgrade: websocket
Upgrade
字段必须设置为websocket
,表示客户端希望连接升级到websocket
协议。
Connection: Upgrade
Connection
必须设置为Upgrade
,表示客户端希望连接升级
Sec-WebSocket-Key
Sec-WebSocket-Key
是随机字符串,服务器会使用它来构造出一个SHA-1的信息摘要,将Sec-WebSocket-Key
加上一个特殊的字符串,然后计算出SHA-1摘要,之后在进行BASE-64编码,最终结果作为Sec-WebSocket-Accept
头的值返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为WebSocket协议。
Sec-WebSocket-Version
Sec-WebSocket-Version
表示支持的WebSocket版本,RFC6455要求使用的版本是13,之前草案的版本均应当弃用。
Origin
Origin
字段是可选的,通常用来表示在浏览器中发起此WebSocket连接所在的页面,类似于Referer
。但是与Referer
不同的是,Origin
只包含了协议和主机名称。
WebSocket与 Socket有什么区别呢?
在软件通信的七层架构中下三层结构偏向于数据通信,上三层偏向于数据处理,中间的传输层则是连接上三层和下三层之间的桥梁,每一层都做着不同的工作,上层协议 依赖于下层协议。
Socket其实并不是一个协议,而是应用层与TCP/IP协议簇通信的中间软件抽象层,它是一组接口。当两台主机通信的时候,会让Socket去组织数据以符合指定的协议。TCP连接则更依赖于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。
WebSocket则是一个典型的应用层协议,总体而言,Socket是传输控制层协议,WebSocket则是应用层协议。
构造函数
首先需要通过调用WebSocket的构造函数来创建WebSocket连接,构造函数会返回一个WebSocket实例,用来监听事件。
WebSocket构造函数需要传入一个URL参数和一个可选的协议参数
var ws = new WebSocket(url, [protocol]);
console.log(ws);
构造函数的第一个参数是URL,WebSocket协议定义了两种URL方案,WS和WSS分别代表了客户端和服务端之间未加密的通信。WS(WebSocket)类似于HTTP的URL,WSS(WebSocket Security)的URL表示连接是基于安全传输层(TLS/SSL),和HTTPS的连接使用的是同样的安全机制。URL参数需要以ws://
或wss://
开头,如果URL存在语法错误构造函数会抛出异常。
构造函数的第二个参数是协议名称,是可选的,服务端和客户端使用的协议必须保持一致,这样收发消息时彼此才能理解。虽然可以定义一个或多个客户端使用的协议,但服务端只会选择一个来使用,一个客户端和一个服务端只能有一个协议,而且都必须基于WebSocket。协议名称参数支持XMPP(Extensible Messaging and Presence Protocol)、SOAP(Simple Object Access Protocol)或自定义协议。
WebSocket对象的属性
ws.readyState
只读属性readState
表示当前连接状态,可选值包括
- 0
WebSocket.CONNECTING
表示连接正在建立中但尚未建立 - 1
WebSocket.OPEN
表示连接已经建立可以发送消息进行通信 - 2
WebSocket.CLOSING
表示连接正在进行关闭握手 - 3
WebSocket.CLOSED
表示连接已经关闭或连接不能打开
ws.bufferedAmount
只读属性bufferedAmount
表示已经被send()
放入正在队列中等待传输,但还没有发出的UTF-8
文本字节数。当需要检查传输数据大小时,尤其是客户端传输大量数据时,虽然send()
方法会马上执行,但数据并不是马上传输。浏览器会缓存应用流出的数据,可以使用bufferedAmount
属性检查已经进入队列但未被传输的数据大小。bufferedAmount
值不包括协议框架、操作系统缓存和网络软件的开销。
例如:使用bufferedAmount
属性每秒更新发送,如果网络不能处理这个频率会自动适应。
//判断浏览器是否支持WebSocket
if(window.WebSocket)
{
console.log("This browser supports WebSocket");
}
else
{
console.log("This browser does not support WebSocket");
}
//10K
var THRESHOLD = 10240;
//建立连接
var url = "ws://echo.websocket.org";
var ws = new WebSocket(url);
//监听握手请求
ws.onopen = function()
{
setInterval(function(){
//如果缓存未满时发送数据,使用bufferedAmount属性发送数据可以避免网络饱和。
if(ws.bufferedAmount < THRESHOLD){
ws.send(getApplicationState());
}
}, 1000);
}
ws.protocol
在构造函数中protocol
参数会让服务器知道客户端使用的WebSocket协议,WebSocket对象的protocol
属性是指最终服务器确定下来的协议名称,当服务器没有选择客户端提供的协议或在连接握手之前,此属性为空。