websocket与node.js的完美结合
发布于 13 年前 作者 kongwu 108082 次浏览 最后一次编辑是 8 年前

之所以写下此文,是我觉得越是简单的技术往往能发挥越重要的作用,随着各种新的技术的诞生,实时web技术已经走进我们。websocket和node.js使开发实时应用非常简单,同时性能也非常高。 <br/> <br/><strong>关于websocket</strong> <br/> <br/>websocket是html5的重要feature,它直接在浏览器上对与socket的支持,这给了web开发无限的想象,虽然以前也有flash socket+js的实现,不过毕竟不稳定,而且兼容性有很多问题,当然websocket的普及也依赖于支持html5标准的浏览器的更新,目前只有chrome、safari、firefox 4.0等少数浏览器可以支持,不过大势所驱,加上智能移动设备的普及,websocket可以有更大的作为。 <br/> <br/>他解决了web实时化的问题,相比传统http有如下好处: <br/><ul> <br/> <li>一个WEB客户端只建立一个TCP连接</li> <br/> <li>Websocket服务端可以推送(push)数据到web客户端.</li> <br/> <li>有更加轻量级的头,减少数据传送量</li> <br/></ul> <br/>本文来重点来分析下。 <br/><h2>websocket的原理和应用</h2> <br/>在继续本文之前,让我们了解下websocket的原理: <br/> <br/>websocket通信协议实现的是基于浏览器的原生socket,这样原先只有在c/s模式下的大量开发模式都可以搬到web上来了,基本就是通过浏览器的支持在web上实现了与服务器端的socket通信。 <br/> <br/>WebSocket没有试图在HTTP之上模拟server推送,而是直接在TCP之上定义了帧协议,因此WebSocket能够支持双向的通信。 <br/> <br/>首先来介绍下websocket客户端与服务端建立连接的过程: <br/> <br/>先用js创建一个WebSocket实例,使用ws协议建立服务器连接,ws://www.cnodejs.org:8088 <br/> <br/>ws开头是普通的websocket连接,wss是安全的websocket连接,类似于https。 <br/> <br/>客户端与服务端建立握手,发送如下信息: <br/><pre><code>GET /echo HTTP/1.1 <br/>Upgrade: WebSocket <br/>Connection: Upgrade <br/>Host: www.cnodejs.org:8088 <br/>Origin: http://www.cnodejs.com</pre></code> <br/>服务端会发回如下: <br/><pre><code>HTTP/1.1 101 Web Socket Protocol Handshake <br/>Upgrade: WebSocket <br/>Connection: Upgrade <br/>WebSocket-Origin: http://www.cnodejs.org <br/>WebSocket-Location: ws://www.cnodejs.org:8088/echo</pre></code> <br/> <br/>具体的ws协议,可以参考: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 <br/> <br/>我们在开发过程中不需要考虑协议的细节,因为websocket API已经帮我们封装好了。 <br/> <br/>需要注意的是所有的通信数据都是以”\x00″开头以”\xFF”结尾的,并且都是UTF-8编码的。 <br/> <br/>这个过程类似于http的建立连接过程,不同的是,建立连接之后,接下来客户端和服务端的任何交互都需要再有这个动作。客户端通过websocket API提供的如下4个事件进行编程: <br/><ul> <br/> <li>onopen 建立连接后触发</li> <br/> <li>onmessage 收到消息后触发</li> <br/> <li>onerror 发生错误时触发</li> <br/> <li>onclose 关闭连接时触发</li> <br/></ul> <br/>让我们全面了解一下websocket API,他其实非常简单,下面是所有的API: <br/><pre class=“brush:js”>[Constructor(in DOMString url, in optional DOMString protocols)] <br/>[Constructor(in DOMString url, in optional DOMString[] protocols)] <br/>interface WebSocket { <br/>readonly attribute DOMString url; <br/> <br/>// ready state <br/>const unsigned short CONNECTING = 0; <br/>const unsigned short OPEN = 1; <br/>const unsigned short CLOSING = 2; <br/>const unsigned short CLOSED = 3; <br/>readonly attribute unsigned short readyState; <br/>readonly attribute unsigned long bufferedAmount; <br/> <br/>// networking <br/>attribute Function onopen; <br/>attribute Function onmessage; <br/>attribute Function onerror; <br/>attribute Function onclose; <br/>readonly attribute DOMString protocol; <br/>void send(in DOMString data); <br/>void close(); <br/>}; <br/>WebSocket implements EventTarget;</pre> <br/>详细的websocket API,可以参考此文: http://dev.w3.org/html5/websockets/ <br/><h2>node.js与websocket的结合</h2> <br/><span style=“font-weight: normal;”>终于讲到了正题了,node.js如何与websocket结合,websocket API是基于事件的,他是对于客户端而言,而对于服务端来说,如何来处理呢?其实可以简单的理解为实现websocket协议的socket server开发。</span> <br/> <br/><span style=“font-weight: normal;”>node.js天生就是一个高效的服务端语言,可以直接使用javascript直接来处理来自客户端的请求,这样如果服务端这边需要大量的业务逻辑开发,则可以直接使用node开发。通过node和websocket的结合可以开发出很多实时性要求很高的web应用,如游戏、直播、股票、监控、IM等等。</span> <br/> <br/>而node.js如何实现websocket的支持,已经有一个比较成熟的开源系统node-websocket-server: https://github.com/miksago/node-websocket-server,让我们来探究一二: <br/> <br/>其实原理也是很简单就是用node实现了websocket draft-76的协议,同时他对外提供了api,可以方便其他应用程序简化编程。 <br/> <br/>它继承了node的http.Server的事件和方法,这样它简化了服务端的编程,同时可以处理http的请求。 <br/> <br/>为了实现连接之间的通信和消息的广播,它实现了一个manager类,给每一个连接创建一个id,然后在内存中维护一个连接链表,并提供了上线和下线的自动管理。 <br/> <br/>它还提供对以下几个事件的接口: <br/><ul> <br/> <li>listening 当服务器准备好接受客户端请求时</li> <br/> <li>request 当一个http 请求发生时触发</li> <br/> <li>stream</li> <br/> <li>close</li> <br/> <li>clientError</li> <br/> <li>error</li> <br/></ul> <br/>让我们看看一个node-websocket-server提供的一个server的例子: <br/><pre class=“brush: javascript; gutter: true; first-line: 1”>var sys = require(“sys”) <br/> , ws = require(’…/lib/ws/server’); <br/> <br/>var server = ws.createServer({debug: true}); <br/> <br/>// Handle WebSocket Requests <br/>server.addListener(“connection”, function(conn){ <br/> conn.send(“Connection: “+conn.id); <br/> <br/> conn.addListener(“message”, function(message){ <br/> conn.broadcast(”<”+conn.id+"> “+message); <br/> <br/> if(message == “error”){ <br/> conn.emit(“error”, “test”); <br/> } <br/> }); <br/>}); <br/> <br/>server.addListener(“error”, function(){ <br/> console.log(Array.prototype.join.call(arguments, “, “)); <br/>}); <br/> <br/>server.addListener(“disconnected”, function(conn){ <br/> server.broadcast(”<”+conn.id+”> disconnected"); <br/>}); <br/> <br/>server.listen(8000);</pre> <br/><div id=“mcePaste">这个例子非常的简单,可以看到对于websocket的server端开发,我们已经不需要考虑websocket协议的实现,他几乎有着和客户端浏览器上websocket API一样的事件,只有对连接、断开连接、消息、错误等事件进行处理,这样应用的开发就非常的灵活了。</div> <br/><h2><span style=“font-weight: normal;”>实例:用websocket和node.js搭建实时监控系统</span></h2> <br/>通过websocket打通了浏览器和服务端之后,我们就可以尝试搭建一个实际的应用,这里以实时监控系统为例。 <br/> <br/><a href=“http://static.data.taobaocdn.com/up/nodeclub/2011/02/mon_nodejs1.png”><img class=“alignnone size-full wp-image-299” title=“mon_nodejs” src=“http://static.data.taobaocdn.com/up/nodeclub/2011/02/mon_nodejs1.png” alt="" width=“498” height=“246” /></a> <br/> <br/>直接与linux自身监控工具的结合,将监控结果通过websocket直接更到网页上,由于建立了socket长连接,绑定iostat的标准输出的事件,做到了真正的实时。同时可以支持对监控结果的讨论,增加了一个简单的chat,基于事件的通讯中,chat和监控同时发送完全不受影响,所以还可以把更多的事件加入进来。 <br/> <br/>让我们来看看这个过程: <br/> <br/>首先是用node.js捕获iostat的输出: <br/><pre class=“brush: javascript; gutter: true; first-line: 1”>var sys = require(“sys”) <br/> , ws = require(’…/lib/ws/server’); <br/> <br/>var sys = require(‘sys’); <br/>var spawn = require(‘child_process’).spawn; <br/>var mon = spawn(“iostat”,["-I",“5”]);</pre> <br/>spawn可以根据参数启动一个进程,同时可以对stdout, stderr, exit code进行捕获,当这些事件触发时,可以绑定我们的函数,同时捕获其输出。 <br/>这里是iostat的标准输出: <br/><pre><code> <br/>disk0 cpu load average <br/>KB/t tps MB/s us sy id 1m 5m 15m <br/>14.64 4 0.06 7 5 88 0.76 0.95 0.90 <br/></pre></code> <br/>我们捕获他的输出,将其发送到客户端去: <br/><pre class=“brush: javascript; gutter: true; first-line: 1”> mon.stdout.on(‘data’,function(data) { <br/> data = format_string(data); <br/> sys.puts(data); <br/> conn.send("#mon:"+data+""); <br/> });</pre> <br/>客户端也就是浏览器,在收到消息后,对其进行简单的字符串处理,然后就可以直接在网页中输出了。 <br/><pre class=“brush: javascript; gutter: true; first-line: 1”>w.onmessage = function(e) { <br/> var msg = e.data; <br/> if(msg.match(/#mon:/)) { <br/> var monarr = msg.split(":")[1].split(" "); <br/> var body = “”; <br/> for(var item in monarr) { <br/> body += "<td id='io”+item+"’>"+monarr[item]+"</td>" <br/> } <br/> $("#iobody").html(body); <br/> //log(monarr[0]); <br/> <br/> } <br/> else <br/> log(e.data); <br/>}</pre> <br/>这里自定义了一个#mon的简单协议,这样可以对更多类型的输出分开处理。 <br/> <br/>服务端和客户端总共100多行的代码,就已经实现了一个实时服务器性能监控系统。 <br/>全部代码下载地址: http://cnodejs.googlecode.com/svn/trunk/monsocket/examples/ <br/>(注:本程序仅在mac osx下测试通过) <br/> <br/>如果加上RGraph(基于html5),则可以打造更加精美的实时展现:  http://www.rgraph.net/docs/dynamic.html <br/><h2>总结</h2> <br/>这篇文章适合node.js的初学者或者对于websocket不够了解的人,总结起来,就是以下几个点: <br/><ul> <br/> <li>使用websocket API可以开发<strong>实时</strong>web应用</li> <br/> <li>websocket api和 node.js可以很完美的配合</li> <br/> <li>node-websocket-server 封装了websocket协议,使服务端进行websocket的开发,非常的简单</li> <br/> <li>node的易用性,使其在服务端略加编程,即可以打造一个完美的后台服务</li> <br/> <li>node的事件驱动的机制保证了系统的高效,可以使用<span style=“font-family: sans-serif; line-height: 22px;”>EventEmitter定义自己的事件触发</span></li> <br/> <li><span style=“font-family: sans-serif;”><span style=“line-height: 22px;”>对于命令行输出可以使用spawn来捕获,通过在web应用中充分利用linux的各种系统工具</span></span></li> <br/></ul> <br/><strong>参考资料</strong> <br/><ul> <br/> <li>http://nodejs.org/</li> <br/> <li>http://dev.w3.org/html5/websockets/</li> <br/> <li>http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76</li> <br/> <li>https://github.com/miksago/node-websocket-server</li> <br/></ul>

17 回复

研究了下websocket和Comet技术,websocket将服务器端推送的问题变得简单。相比long polling和iframe隐藏域来说更加容易使用。nodejs的事件驱动机制能更好地支持这种服务器端推送。只要在得到连接时注册监听函数监听在某个消息上。当服务器需要统一推送数据时发送消息,则会调用callback函数向客户端推送数据。程序的设计会变得简单。

html5浏览器和nodejs基于相同的websocket协议,使得用单纯js也可以进行客户端/服务器之间的socket编程。同时相对于comet等技术,websocket简化了实时web应用的开发。

websocket 是好东西··可惜目前have a protocol-level security issue

[…] 正好试验下node和websocket的实时特性,原理可以参加我的另外一篇:《websocket与node.js的完美结合》, […]

[…] 1、websocket与node.js的完美结合( http://cnodejs.org/blog/?p=273 ) 。从这里了解到node-websocket-server […]

其实不就是回到了c/s结构时代么,走一圈还是回到原点。

其实B本身也是C。

请问下为什么我客户端打开就是关闭连接类?浏览器是chrome 16.0的。。nodejs没什么问题,websocket-server也装好了。。

是不是下载错了,源码明明还有挺多错误的。例子好像也有错。

你的浏览器升级了,支持了更高版本的websocket协议,而miksago的node-websocket-server的实现并没有及时更新,所以连接就报错。我在chrome16里显示的是’Sec-WebSocket-Accept’ header is missing错误。请参照作者对这个issue的留言:https://github.com/miksago/node-websocket-server/issues/72

请问websocket是否需要处理粘包等问题?socket.io下又是否将粘包等问题处理过了呢

想玩websocket最好还是用ws吧 socket.io都切换过来用它了

websocket在协议层面是考虑了粘包问题的,它有消息边界,首先它按frame传送,每个frame第一位表示是否是最后一个frame,其次每个frame都有length位。

说实话,还是flash socket简单易用多了~

websocket性能超级好,在对视频,请求比较频繁但是报头占比例较大的服务而言,是非常不错的选择,尤其结合了nodejs的异步处理。

好烦人的 br号,严重妨碍理解代码啊

回到顶部