天天看点

WebSocket入门

HTML5 拥有众多引人注目的新特性,如 Canvas、本地存储、多媒体编程接口、WebSocket 等等。其中,WebSocket 的出现使得浏览器提供对 Socket 的支持成为可能,从而在浏览器和服务器之间提供了一个基于 TCP 连接的双向通道。使用 WebSocket,web开发人员可以很方便地构建实时 web 应用。

本文首先介绍WebSocket的基本知识,然后介绍两个常用的WebSocket库——

socket.io SockJS-tornado

背景

以前,很多网站使用轮询实现推送技术。轮询是在特定的的时间间隔(比如1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给浏览器。轮询的缺点很明显,浏览器需要不断的向服务器发出请求,然而HTTP请求的header是非常长的,而实际传输的数据可能很小,这就造成了带宽和服务器资源的浪费。

Comet

使用了AJAX改进了轮询,可以实现双向通信。但是Comet依然需要发出请求,而且在Comet中,普遍采用了长链接,这也会大量消耗服务器带宽和资源。

于是,WebSocket协议应运而生。

WebSocket协议

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器通过 TCP 连接直接交换数据。WebSocket 连接本质上是一个 TCP 连接。

WebSocket在数据传输的稳定性和数据传输量的大小方面,具有很大的性能优势。

Websocket.org

比较了轮询和WebSocket的性能优势:

WebSocket入门

从上图可以看出,WebSocket具有很大的性能优势,流量和负载增大的情况下,优势更加明显。

例子

浏览器请求

GET / HTTP/1.1

Upgrade: websocket

Connection: Upgrade

Host: example.com

Origin: null

Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==

Sec-WebSocket-Version: 13

服务器回应

HTTP/1.1 101 Switching Protocols

Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=

Sec-WebSocket-Origin: null

Sec-WebSocket-Location: ws://example.com/

在请求中的

Sec-WebSocket-Key

是随机的,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把

Sec-WebSocket-Key

加上一个魔幻字符串

258EAFA5-E914-47DA-95CA-C5AB0DC85B11

。使用 SHA-1 加密,之后进行 BASE-64编码,将结果作为

Sec-WebSocket-Accept

头的值,返回给客户端。

浏览器兼容性

最新的主流浏览器对WebSocket支持良好:

  • Chrome 4+
  • Firefox 4+
  • Internet Explorer 10+
  • Opera 10+
  • Safari 5+

Socket.IO

是一个封装了WebSocket的JavaScript模块。因为完全使用JavaScript编写,所以在每个浏览器和移动设备中都可以方便地通过Socket.IO使用WebSocket。

服务器端

var io = require('socket.io').listen(80);

io.sockets.on('connection', function (socket) {

  socket.emit('news', { hello: 'world' });

  socket.on('my other event', function (data) {

    console.log(data);

  });

});

客户端

var socket = io.connect('http://localhost');

  socket.on('news', function (data) {

    socket.emit('my other event', { my: 'data' });

是一个配合

Tornado

框架使用的Python模块。

from tornado import web, ioloop

from sockjs.tornado import SockJSRouter, SockJSConnection

class EchoConnection(SockJSConnection):

    def on_message(self, msg):

        self.send(msg)

if __name__ == '__main__':

    EchoRouter = SockJSRouter(EchoConnection, '/echo')

    app = web.Application(EchoRouter.urls)

    app.listen(9999)

    ioloop.IOLoop.instance().start()