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

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

 

背景

以前,很多网站使用轮询实现推送技术。轮询是在特定的的时间间隔(比如1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给浏览器。

轮询的缺点很明显,浏览器需要不断的向服务器发出请求,然而HTTP请求的header是非常长的,而实际传输的数据可能很小,这就造成了带宽和服务器资源的浪费。

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

于是,WebSocket协议应运而生。

 

WebSocket协议

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

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

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

websocket-profile-and-nginx-config

从上图可以看出,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
Upgrade: websocket
Connection: Upgrade
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

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

Socket.IO设计的目标是构建能够在不同浏览器和移动设备上良好运行的实时应用,如实时分析系统、二进制流数据处理应用、在线聊天室、在线客服系统、评论系统、WebIM等。目前,Socket.IO已经支持主流PC浏览器,例如IE、Safari、Chrome、Firefox、Opera 等)和移动平台上的浏览器(iOS平台下的Safari、Android平台下的基于Webkit的浏览器等。

服务器端

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);
  });
});

客户端

<script src="socket.io.min.js"></script>
<script>
  var socket = io.connect('http://localhost:8080');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

Socket.IO已经具有众多强大功能的模块和扩展API,例如:

session.socket.iohttp session中间件,进行session相关操作

socket.io-cookie:cookie解析中间件

session-web-sockets:以安全的方式传递Session

socket-logger:JSON格式的记录日志工具

websocket.MQ:可靠的消息队列

socket.io-mongo:使用MongoDB的适配器

socket.io-redis:Redis的适配器

socket.io-parser:服务端和客户端通讯的默认协议实现模块等。

 

SockJS-tornado

SockJS-tornado是一个配合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()

 

WebSocket 的 Nginx 配置

Nginx 从 1.3 开始支持 WebSocket , 详见官方文档: NGINX Announces Support for WebSocket Protocol

Nginx, Inc., the high performance web company, today announced support for the WebSocket Protocol in the latest iteration of NGINX version 1.3. Available immediately, the functionality of the WebSocket Protocol is accessible within the core product. 

 

 

nginx 配置WebSocket

先用 ws 模块写一个简单的 WebSocket 服务器:

Server = require('ws').Server
wss = new Server port: 3000
wss.on 'connection', (ws) ->
  console.log 'a connection'
  ws.send 'started'
console.log 'server started'

然后修改 Hosts, 添加, 比如 ws.repo , 指向 127.0.0.1 

127.0.0.1     ws.repo

然后是 Nginx 配置

server {
  listen 80;
  server_name ws.repo;

  location / {
    proxy_pass http://127.0.0.1:3000/;
    proxy_redirect off;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

Reload Nginx

/etc/init.d/nginx reload

然后从浏览器控制台尝试链接, OK

new WebSocket('ws://ws.repo/')

或者通过 Upstream 的写法:

upstream ws_server {
  server 127.0.0.1:3000;
}

server {
  listen 80;
  server_name ws.repo;

  location / {
    proxy_pass http://ws_server/;
    proxy_redirect off;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
  }
}

WebSocket 先是通过 HTTP 建立连接, 
然后通过 101 状态码, 表示切换协议,在配置里是 Upgrade 

Nginx配置WebSocket官方教程: WebSocket proxying

 

 

参考推荐:

WebSocket API简介 (推荐)