websocket基础使用与实例
对websocket做一个简单的总结。之前的实习过程中基本上不需要使用websocket,使用ajax基本就都能完成所需要的数据请求工作了,因此对于websocket的掌握还是不精。
HTML的基本情况
HTTP的生命周期通过 Request 来界定,也就是一个 Request 一个 Response ,那么在 HTTP1.0 中,这次HTTP请求就结束了。
在HTTP1.1中进行了改进,使得有一个keep-alive,也就是说,在一个HTTP连接中,可以发送多个Request,接收多个Response。但是请记住 Request = Response , 在HTTP中永远是这样,也就是说一个request只能有一个response。而且这个response也是被动的,不能主动发起。
那么为什么需要websocket呢?因为 HTTP 协议有一个缺陷:通信只能由客户端发起。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询"和"long poll":
ajax轮询
原理:客户端定时向服务器发送Ajax请求,服务器接到请求后马上返回响应信息并关闭连接。
优点:后端程序编写比较容易。
缺点:请求中有大半是无用,浪费带宽和服务器资源
。 同时也需要服务器有很快的处理能力。
实例:适于小型应用。
long poll 长轮询
原理:客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:在无消息的情况下不会频繁的请求,耗费资源小。
缺点:服务器hold连接会消耗资源,返回数据顺序无保证,难于管理维护。 需要服务器有很高的并发。
实例:WebQQ、Hi网页版、Facebook IM。
websocket原理
为了解决上述问题,在HTML5中推出了websocket协议。
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
其他特点包括:
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
接下来看一下请求和返回数据头
这是一个个典型的 Websocket
握手请求头:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
可以看到,首先多了下面两行
Upgrade: websocket
Connection: Upgrade
这个就是Websocket的核心了,告诉 Apache
、 Nginx
等服务器:我发起的是Websocket协议。
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
首先, Sec-WebSocket-Key
是一个 Base64 encode
的值,这个是浏览器随机生成的,告诉服务器:拿这个密钥证明你的身份。
然后, Sec-WebSocket-Protocol
表示client(应用程序)支持的协议列表,server选择一个或者没有可接受的协议响应之
最后, Sec-WebSocket-Version
是告诉服务器所使用的 Websocket Draft
(协议版本)。
然后服务器会返回下列东西,表示已经接受到请求, 成功建立Websocket。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
这里告诉客户,我已经成功切换协议了
Upgrade: websocket
Connection: Upgrade
依然是固定的,告诉客户端即将升级的是 Websocket
协议,而不是mozillasocket,lurnarsocket或者shitsocket。
然后, Sec-WebSocket-Accept
这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key
。 服务器:这一串证明我的身份。
后面的, Sec-WebSocket-Protocol
则是表示最终使用的协议。
websoket链接的简单例子
<input type="text" id="input">
<button id="button">click</button>
<div id="res"></div>
<script type="text/javascript">
var ws = new WebSocket('ws://echo.websocket.org');
var res = document.getElementById('res');
ws.onopen = function() {
res.innerHTML = 'Connected';
};
ws.onclose = function() {
res.innerHTML = 'Closed';
};
ws.onmessage = function(e) {
console.log(e);
res.innerHTML = e.data;
};
var button = document.getElementById('button');
var input = document.getElementById('input');
button.addEventListener('click', function() {
ws.send(input.value);
});
</script>
打开网页,可以看到显示已连接
输入123并点击按钮,可以看到返回了123
然后看一下返回的"e"是什么
控制台中看到ws数据请求
Frames中列出了data数据,绿色的为发送出去的,白色的为接收到的
客户端的 API
WebSocket 客户端的 API 如下。这里转载了阮一峰老师的博客内容,原链接在这里
WebSocket 构造函数
WebSocket 对象作为一个构造函数,用于新建 WebSocket 实例。
var ws = new WebSocket('ws://localhost:8080');
执行上面语句之后,客户端就会与服务器进行连接。
实例对象的所有属性和方法清单,参见这里。
webSocket.readyState
readyState
属性返回实例对象的当前状态,共有四种。
- CONNECTING:值为0,表示正在连接。
- OPEN:值为1,表示连接成功,可以通信了。
- CLOSING:值为2,表示连接正在关闭。
- CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
下面是一个示例。
switch (ws.readyState) { case WebSocket.CONNECTING: // do something break; case WebSocket.OPEN: // do something break; case WebSocket.CLOSING: // do something break; case WebSocket.CLOSED: // do something break; default: // this never happens break; }
webSocket.onopen
实例对象的onopen
属性,用于指定连接成功后的回调函数。
ws.onopen = function () { ws.send('Hello Server!'); }
如果要指定多个回调函数,可以使用addEventListener
方法。
ws.addEventListener('open', function (event) { ws.send('Hello Server!'); });
webSocket.onclose
实例对象的onclose
属性,用于指定连接关闭后的回调函数。
ws.onclose = function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event }; ws.addEventListener("close", function(event) { var code = event.code; var reason = event.reason; var wasClean = event.wasClean; // handle close event });
webSocket.onmessage
实例对象的onmessage
属性,用于指定收到服务器数据后的回调函数。
ws.onmessage = function(event) { var data = event.data; // 处理数据 }; ws.addEventListener("message", function(event) { var data = event.data; // 处理数据 });
注意,服务器数据可能是文本,也可能是二进制数据(blob
对象或Arraybuffer
对象)。
ws.onmessage = function(event){ if(typeof event.data === String) { console.log("Received data string"); } if(event.data instanceof ArrayBuffer){ var buffer = event.data; console.log("Received arraybuffer"); } }
除了动态判断收到的数据类型,也可以使用binaryType
属性,显式指定收到的二进制数据类型。
// 收到的是 blob 数据 ws.binaryType = "blob"; ws.onmessage = function(e) { console.log(e.data.size); }; // 收到的是 ArrayBuffer 数据 ws.binaryType = "arraybuffer"; ws.onmessage = function(e) { console.log(e.data.byteLength); };
webSocket.send()
实例对象的send()
方法用于向服务器发送数据。
发送文本的例子。
ws.send('your message');
发送 Blob 对象的例子。
var file = document .querySelector('input[type="file"]') .files[0]; ws.send(file);
发送 ArrayBuffer 对象的例子。
// 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]; } ws.send(binary.buffer);
webSocket.bufferedAmount
实例对象的bufferedAmount
属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。
var data = new ArrayBuffer(10000000); socket.send(data); if (socket.bufferedAmount === 0) { // 发送完毕 } else { // 发送还没结束 }
webSocket.onerror
实例对象的onerror
属性,用于指定报错时的回调函数。
socket.onerror = function(event) { // handle error event }; socket.addEventListener("error", function(event) { // handle error event });
websocket支持跨域
websocket协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
我们可以看到请求头中有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。
正是因为有了Origin这个字段,所以WebSocket才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出许可回应。