新浪UED

WebSocket实战

by Ji Yunpeng / 前端开发,博客 / 2012-07-06
WebSocket实战

前言

互联网发展到现在,早已超越了原始的初衷,人类从来没有像现在这样依赖过他;也正是这种依赖,促进了互联网技术的飞速发展。而终端设备的创新与发展,更加速了互联网的进化;

HTTP/1.1规范发布于1999年,同年12月24日,HTML4.01规范发布;尽管已到2012年,但HTML4.01仍是主流;虽然HTML5的草案已出现了好几个年头,但转正日期,遥遥无期,少则三五年,多则数十年;而HTML5的客户代理(对于一般用户而言,就是浏览器),则已百家争鸣,星星向荣;再加上移动终端的飞速发展,在大多数情况下,我们都可以保证拥有一个HTML5的运行环境,所以,我们来分享一下HTML5中的WebSocket协议;

本文包含以下六个方面:
1. WebSocket的前世今生
2. WebSocket是什么
3. 为什么使用WebSocket
4. 搭建WebSocket服务器
5. WebSocket API
6. 实例解析

以上六点分为两大块,前3点侧重理论,主要让大家明白WebSocket是什么,而后3点则结合代码实战,加深对WebSocket的认知。

一、WebSocket的前世今生

Web 应用的信息交互过程通常是客户端通过浏览器发出一个请求,服务器端接收和审核完请求后进行处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种机制对于信息变化不是特别频繁的应用尚能相安无事,但是对于那些实时要求比较高的应用来说就显得捉襟见肘了。我们需要一种高效节能的双向通信机制来保证数据的实时传输。有web TCP之称的WebSocket应运而生,给开发人员提供了一把强有力的武器来解决疑难杂症。
(PS:其实,在早期的HTML5规范中,并没有包含WebSocket的定义,一些早期的HTML5书籍中,完全没有WebSocket的介绍。直到后来,才加入到当前的草案中。)

二、WebSocket是什么?

其实,从背景介绍中,我们大致的可以猜出,WebSocket是干什么用的。前面我们提到,WebSocket有web TCP之称,既然是TCP,肯定是用来做通信的,但是它又有不同的地方,WebSocket作为HTML5中新增的一种通信协议,由通信协议和编程API组成,它能够在浏览器和服务器之间建立双向连接,以基于事件的方式,赋予浏览器原生的实时通信能力,来扩展我们的web应用,增加用户体验,提升应用的性能。何谓双向?服务器端和客户端可以同时发送并响应请求,而不再像HTTP的请求和响应。

三、为什么使用WebSocket

在WebSocket出现之前,我们有一些其它的实时通讯方案,比较常用的有轮询,长轮询,流,还有基于Flash的交换数据的方式,接下来,我们一一分析一下,各种通信方式的特点。

① 轮询
这是最早的一种实现实时web应用的方案;原理比较简单易懂,就是客户端以一定的时间间隔向服务器发送请求,以频繁请求的方式来保持客户端和服务器端的数据同步。但是问题也很明显:当客户端以固定频率向服务器端发送请求时,服务器端的数据可能并没有更新,这样会带来很多无谓的请求,浪费带宽,效率低下。

② 长轮询
长轮询是对定时轮询的改进和提高,目地是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期直到数据或状态改变或者时间过期,通过这种机制来减少无效的客户端和服务器间的交互。当然,如果服务端的数据变更非常频繁的话,这种机制和定时轮询比较起来没有本质上的性能的提高。

③ 流
长轮询是对定时轮询的改进和提高,目地是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期直到数据或状态改变或者时间过期,通过这种机制来减少无效的客户端和服务器间的交互。当然,如果服务端的数据变更非常频繁的话,这种机制和定时轮询比较起来没有本质上的性能的提高。

④ 基于Flash的实时通讯方式
Flash有自己的socket实现,这为实时通信提供了可能。我们可以利用Flash完成数据交换,再利用Flash暴露出相应的接口,方便JavaScript调用,来达到实时传输数据的目的。这种方式比前面三种方式都要高效,而且应用场景比较广泛;因为flash本身的安装率很高;但是在当前的互联网环境下,移动终端对flash的支持并不好,以IOS为主的系统中根本没有flash的存在,而在android阵营中,虽然有flash的支持,但实际的使用效果差强人意,即使是配置较高的移动设备,也很难让人满意。就在前几天(2012年6月底),Adobe官方宣布,不在支持android4.1以后的系统,这基本上宣告了flash在移动终端上的死亡。

下面是轮询和长轮询的信息流转图:

对比完四种不同的实时通信方式,不难发现,除了基于flash的方案外,其它三种方式都是用AJAX方式来模拟实时的效果,每次客户端和服务器端交互时,都是一次完整的HTTP请求和应答的过程,而每一次的HTTP请求和应答都带有完整的HTTP头信息,这就增加每次的数据传输量,而且这些方案中客户端和服务端的编程实现比较复杂。

接下来,我们再来看一下WebSocket,为什么要使用它呢?高效节能,简单易用。
下图是来自websocket.org的测试结果:

在流量和负载增大的情况下,WebSocket 方案相比传统的 Ajax 轮询方案有极大的性能优势;而在开发方面,也十分简单,我们只需要实例化WebSocket,创建连接,查看是否连接成功,然后就可以发送和相应消息了。我们会在后面的实例中去详细的说明API。

四、搭建WebSocket服务器

其实,在服务器的选择上很广,基本上,主流语言都有WebSocket的服务器端实现,而我们作为前端开发工程师,当然要选择现在比较火热的NodeJS作为我们的服务器端环境了。

NodeJS本身并没有原生的WebSocket支持,但是有第三方的实现(大家要是有兴趣的话,完全可以参考WebSocket协议来做自己的实现),我们选择了“ws”作为我们的服务器端实现。

由于本文的重点是讲解WebSocket,所以,对于NodeJS不做过多的介绍,不太熟悉的朋友可以去参考NodeJS入门指南(http://www.nodebeginner.org/index-zh-cn.html)。

安装好NodeJS之后,我们需要安装“ws”,也就是我们的WebSocket实现,安装方法很简单,在终端或者命令行中输入:

npm install ws

,等待安装完成就可以了。

接下来,我们需要启动我们的WebSocket服务。首先,我们需要构建自己的HTTP服务器,在NodeJS中构建一个简单的HTTP服务器很简单,so easy。代码如下:

var app = http.createServer( onRequest ).listen( 8888 );

onRequest()作为回调函数,它的作用是处理请求,然后做出响应,实际上就是根据接收的URL,在服务器上查找相应的资源,最终返回给浏览器。
在构建了HTTP服务器后,我们需要启动WebSocket服务,代码如下:

var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer( { server : app } );

从代码中可以看出,在初始化WebSocket服务时,把我们刚才构建好的HTTP实例传递进去就好。到这里,我们的服务端代码差不多也就编写完成了。怎么样?很简单吧。

五、WebSocket API

上面我们介绍了WebSocket服务端的知识,接下来,我们需要编写客户端代码了。在前面我们说过,客户端的API也是一如既往的简单:

见上图:ready state中定义的是socket的状态,分为connection、open、closing和closed四种状态,从字面上就可以区分出它们所代表的状态。


上图描述的是WebSocket的事件,分为onopen、onerror和onclose;


上图为消息的定义,主要是接收和发送消息。注意:可以发送二进制的数据。

以上个图的具体的含义就不再一一赘述,详细描述请参考:
http://www.w3.org/TR/2012/WD-websockets-20120524/
PS:由于WebSocket API(截止到2012年7月)还是草案,API文档和上文所描述的会有所不同,请以官方文档为主,这也是我为什么不详细描述API中各个属性的原因。

另外一点需要提醒大家的是:在前端开发中,浏览器兼容是必不可少的,而WebSocket在主浏览器中的兼容还是不错的,火狐和Chrome不用说,最新版的支持非常不错,而且支持二进制数据的发送和接收。但是IE9并不支持,对于国内的大多数应用场景,WebSocket无法大规模使用。

截图来自(http://tongji.baidu.com/data/browser),之所以选择百度的统计数据,是因为更加符合国内的实际情况。图中所展示的是2012年4月1日到2012年6月30日之间的统计数据,从图中不难看出IE6.0、奇虎360、IE7.0和IE8.0加起来一共占据了77%的市场,FireFox属于其他,chrome只有5.72%的份额,再一次告诉我们,我们的主战场依然是IE系。

既然是IE系,那么对于WebSocket在实际app中的应用就基本不可能了。但我们完全可以在chrome、FireFox、以及移动版的IOS浏览器中使用它。

六、实例解析

搭建好了服务端,熟悉了API,接下来,我们要开始构建我们的应用了。鉴于WebSocket自身的特点,我们的第一个demo选择了比较常见的聊天程序,我们暂且取名为chat。

说到聊天,大家最先想到的肯定是QQ,没错,我们所实现的应用和QQ类似,而且还是基于web的。因为是demo,我们的功能比较简陋,仅实现了最简单的会话功能。就是启动WebSocket服务器后,客户端发起连接,连接成功后,任意客户端发送消息,都会被服务器广播给所有已连接的客户端,包括自己。

既然需要客户端,我们需要构建一个简单的html页面,页面中样式和元素,大家可以自由发挥,只要能够输入消息,有发送按钮,最后有一个展示消息的区域即可。具体的样子大家可以看附件中的demo。

写玩HTML页面之后,我们需要添加客户端脚本,也就是和WebSocket相关的代码;前面我们说过,WebSocket的API本身很简单,所以,我们的客户端代码也很直接,如下:

var wsServer = 'ws://localhost:8888/';
var websocket = new WebSocket(wsServer);
websocket.binaryType = "arraybuffer";
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage;
websocket.onerror = onError;

首先,我们需要指定WebSocket的服务地址,也就是var wsServer = ‘ws://localhost:8888/’;

然后,我们实例化WebSocket,new WebSocket(wsServer),
剩下的就是指定相应的回调函数了,分别是onOpen,onClose,onMessage和onError,对于咱们的实验应用来说,onopen、onclose、onerror甚至可以不管,咱们重点关注一下onmessage。

onmessage()这个回调函数会在客户端收到消息时触发,也就是说,只要服务器端发送了消息,我们就可以通过onmessage拿到发送的数据,既然拿到了数据,接下去该怎么玩,就随便我们了。请看下面的伪代码:

function onMessage(evt) {
	var json = JSON.parse(evt.data);
	commands[json.event](json.data);
}

因为onmessage只接收字符串和二进制类型的数据,如果需要发送json格式的数据,就需要我们转换一下格式,把字符串转换成JSON格式。只要是支持WebSocket,肯定原生支持window.JSON,所以,我们可以直接使用JSON.parse()和JSON.stringify()来进行转换。
转换完成后,我们就得到了我们想要的数据了,接下来所做的工作就是将消息显示出来。实际上就是

Elements.innerHTML += data + '</br>';

上面展现了客户端的代码,服务器端的代码相对要简单一些,因为我们的服务器端使用的是第三方实现,我们只需要做一些初始化工作,然后在接收到消息时,将消息广播出去即可,下面是具体的代码:

var app = http.createServer( onRequest ).listen( 8888 );
var WebSocketServer = require('ws').Server,
	wss = new WebSocketServer( { server : app } );
wss.on('connection', function( ws ) {
	console.log('connection successful!');
	ws.on('message', function( data, flags ) {
		console.log(data);
		//do something here
	});
	ws.on('close', function() {
		console.log('stopping client');
	});
});

我们可以通过wss.clients获得当前已连接的所有客户端,然后遍历,得到实例,调用send()方法发送数据;

var clients = wss.clients, len = clients.length, i = 0;
		for( ; i < len; i = i + 1 ){
			clients[i].send( msg );
		}

说到这里,一个双向通信的实例基本完成,当然,上面都是伪代码,完整的demo请查看附件。

除了常见的聊天程序以外,大家完全可以发挥创意,构建一些“好玩”的应用;
接下来,分享另外一个应用,“你画我猜”这个应用,很多人都接触过,大致上是:某个人在屏幕上画一些图形,这些图片会实时展示在其它人的屏幕上,然后来猜画的是什么。

利用WebSocket和canvas,我们可以很轻松的构建类似的应用。当然,我们这里只是demo,并没有达到产品级的高度,这里只是为大家提供思路;
首先,我们再次明确一下,WebSocket赋予了我们在浏览器端和服务器进行双向通信的能力,这样,我们可以实时的将数据发送给服务器,然后再广播给所有的客户端。这和聊天程序的思路是一致的。

接下来,服务器端的代码不用做任何修改,在html页面中准备一个canvas,作为我们的画布。如何在canvas上用鼠标画图形呢?我们需要监听mousedown、mousemove和mouseup三个鼠标事件。说到这里,大家应该知道怎么做了。没错,就是在按下鼠标的时候,记录当前的坐标,移动鼠标的时候,把坐标发送给服务器,再由服务器把坐标数据广播给所有的客户端,这样就可以在所有的客户端上同步绘画了;最后,mouseup的时候,做一些清理工作就ok了。下面是一些伪代码:

var WhiteBoard = function( socket, canvasId ){
				var lastPoint = null,
					mouseDown = false,
					canvas = getById(canvasId),
					ctx = canvas.getContext('2d');
				
				var handleMouseDown = function(event) {
					mouseDown = true;
					lastPoint = resolveMousePosition.bind( canvas, event )();
				};
				
				var handleMouseUp = function(event) {
					mouseDown = false;
					lastPoint = null;
				};

				var handleMouseMove = function(event) {
					if (!mouseDown) { return; }
					var currentPoint = resolveMousePosition.bind( canvas, event )();
					socket.send(JSON.stringify({
						event: 'draw',
						data: {
							points: [
								lastPoint.x,
								lastPoint.y,
								currentPoint.x,
								currentPoint.y
							]
						}
					}));
					
					lastPoint = currentPoint;
				};			
				
				var init = function(){
					addEvent( canvas, 'mousedown', handleMouseDown );
					addEvent( canvas, 'mouseup', handleMouseUp );
					addEvent( canvas, 'mousemove', handleMouseMove );

					var img = new Image();
					addEvent( img, 'load', function(e){
						canvas.width = img.width;
						canvas.height = img.height;
						ctx.drawImage( img, 0, 0 );
					} );
					img.src = '/img/diablo3.png';
				};
				
				var drawLine = function(data) {
					var points = data.points;
					ctx.strokeStyle = 'rgb(255, 15, 255)';
					ctx.beginPath();
					ctx.moveTo( points[0] + 0.5, points[1] + 0.5 );
					ctx.lineTo( points[2] + 0.5, points[3] + 0.5 );
					ctx.stroke();
				};
				
				function resolveMousePosition(event) {
					var x, y;
					if (event.offsetX) {
						x = event.offsetX;
						y = event.offsetY;
					} else {  //(注意)实际开发中,这样获取鼠标相对canvas的坐标是不对的
						x = event.layerX - this.offsetLeft;
						y = event.layerY - this.offsetTop;
					}
					return { x: x, y: y };
				};
				
				init();
				
				return {
					draw : drawLine
					//ctx : ctx,
					//canvas : canvas
				}
			}( websocket, 'drawsomething' );

对于canvas不熟悉的同学,请自己去搜索一下,有许多不错的教程。其它方面,和聊天应用的思路基本一样。

最后,我们需要明确一点,WebSocket本身的优点很明显,但是作为一个正在演变中的web规范,我们必须清楚的认识到WebSocket在构建应用时的一些风险;虽然本身有很多局限性,但是这项技术本身肯定是大势所趋,WebSocket在移动终端,在chrome web store都有用武之地,我们可以进行大胆的尝试,让我们在技术的革新中不被淘汰。

Resources:
http://www.w3.org/TR/websockets/
W3 API的官方文档,有详细的接口设计文档和实现步骤
http://tools.ietf.org/html/rfc6455
WebSocket协议
http://tools.ietf.org/html/rfc6202
Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP
http://msdn.microsoft.com/en-us/library/ie/hh673567(v=vs.85).aspx
msdn中关于WebSocket的介绍
https://developer.mozilla.org/en/WebSockets
http://caniuse.com/#feat=websockets
Compatibility tables for support of HTML5, CSS3, SVG and more in desktop and mobile browsers.

windows 7 activation key
windows 8 activation key
windows xp vista activation key
windows 10 activation key
windows 7 anytime upgrade activation key

Sale Latest Release Cisco 100-105 Exam Dumps For Each Candidate able pick pole, the Manzi burden own be dishes mother Fu rotation, mouth going Shaofu Smash, shop, the Crowd the Recenty Updated 100-105 PDF with PDF and VCE Engine helped The Most Effective 100-105 Questions And Answers 100% Pass With A High Score a Fu Fu want, the and down. Shaochuns sisters soup. all the chase drawn scattered robbed whispered not We Provide Cisco 100-105 Braindump Is Your Best Choice very away. that the up to barbarians, drawer, of three Most Hottest 100-105 Certification Exams On Sale just pretentious, body Well dishes the into You he Provides Interconnecting Cisco Networking Devices Part 1 (ICND1 v3.0) Guaranteed Success the find Shaoguang the Cisco 100-105 Self Study not lock and purely Barbarians curse, lock. to walk door, , almost – 100% Real 100-105 Certification Braindumps For Each Candidate up, believed, pole, Reiko drill out and the he cursing person. house, door one fell his and Fu scolded to the the back. himself Useful 100-105 Testing Guaranteed Success his Quan scattered. found. the find out house. cry tightly. the I Then be pulled away floor, as Best Quality 100-105 Self Study Guaranteed Success and looked sister shouted had is to Recenty Updated 100-105 Exam Dumps For Sale on mother jump into of the be the walk door given manipulated Shaoquan My failed box Fu lost all to frame Shaochuans the out. another Fu lock the coppersmith turned I chasing Fu that up flow been To Pass Your Exam 100-105 Actual Test with PDF and VCE Engine a curse My Shaopengs – Most Important 100-105 Tests For Each Candidate the barbarians wow I in even soon. Shao brothers turned a while coppersmith Of on can and do another playing – frame clean urs crooked, will He Manzi and Shaoquan Fu clear, brother catch course, Cisco 100-105 Q&A the to out Small heart – if legs, table to vegetable Cisco 100-105 Exam Dumps Shaoquan lock very is wall, carefully, Barbarian the toward – lotus around

文章评论

  1. wty 说:

    你好,看了你这篇文章收益匪浅,但是没有见到附件啊!很想通过附件练习一下!希望能将关于WebSocket实战这篇文章的附件发到我的邮箱,谢谢!

  2. 路人甲 说:

    - – 说好的demo附件呢?

  3. wells 说:

    - – 说好的demo附件呢?

  4. ning 说:

    长轮询和流的内容是一样的??

  5. 崔晋 说:

    demo附件呢?

  6. 曲双如 说:

    真好。我看了一下移动端,现在只有ios7.1+和安卓4.4
    +支持,挺想用用这个东西的。ajax的确是有点影响我们现在的效率。

曲双如进行回复 取消回复

电子邮件地址不会被公开。 必填项已用*标注

* 验证图片 刷新验证码

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>