[技术| 编程·课件·Linux] WebSocket实战

admin · 发布于 2013-05-15 15:44 · 1574 次阅读
1
前言互联网发展到现在,早已超越了原始的初衷,人类从来没有像现在这样依赖过他;也正是这种依赖,促进了互联网技术的飞速发展。而终端设备的创新与发展,更加速了互联网的进化;

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实现,安装方法很简单,在终端或者命令行中输入:
查看源代码
打印帮助

1npm install ws



,等待安装完成就可以了。
接下来,我们需要启动我们的WebSocket服务。首先,我们需要构建自己的HTTP服务器,在NodeJS中构建一个简单的HTTP服务器很简单,so easy。代码如下:
查看源代码
打印帮助

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




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

1var WebSocketServer = require('ws').Server;

2var 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本身很简单,所以,我们的客户端代码也很直接,如下:
查看源代码
打印帮助

1var wsServer = 'ws://localhost:8888/';

2var websocket = new WebSocket(wsServer);

3websocket.binaryType = "arraybuffer";

4websocket.onopen = onOpen;

5websocket.onclose = onClose;

6websocket.onmessage = onMessage;

7websocket.onerror = onError;




首先,我们需要指定WebSocket的服务地址,也就是var wsServer = ‘ws://localhost:8888/’;
然后,我们实例化WebSocket,new WebSocket(wsServer),
剩下的就是指定相应的回调函数了,分别是onOpen,onClose,onMessage和onError,对于咱们的实验应用来说,onopen、onclose、onerror甚至可以不管,咱们重点关注一下onmessage。
onmessage()这个回调函数会在客户端收到消息时触发,也就是说,只要服务器端发送了消息,我们就可以通过onmessage拿到发送的数据,既然拿到了数据,接下去该怎么玩,就随便我们了。请看下面的伪代码:
查看源代码
打印帮助

1function onMessage(evt) {

2    var json = JSON.parse(evt.data);

3    commands[json.event](json.data);

4}




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

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




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

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

02var WebSocketServer = require('ws').Server,

03    wss = new WebSocketServer( { server : app } );

04wss.on('connection', function( ws ) {

05    console.log('connection successful!');

06    ws.on('message', function( data, flags ) {

07        console.log(data);

08        //do something here

09    });

10    ws.on('close', function() {

11        console.log('stopping client');

12    });

13});




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

1var clients = wss.clients, len = clients.length, i = 0;

2        for( ; i < len; i = i + 1 ){

3            clients.send( msg );

4        }




说到这里,一个双向通信的实例基本完成,当然,上面都是伪代码,完整的demo请查看附件。
除了常见的聊天程序以外,大家完全可以发挥创意,构建一些“好玩”的应用;
接下来,分享另外一个应用,“你画我猜”这个应用,很多人都接触过,大致上是:某个人在屏幕上画一些图形,这些图片会实时展示在其它人的屏幕上,然后来猜画的是什么。
利用WebSocket和canvas,我们可以很轻松的构建类似的应用。当然,我们这里只是demo,并没有达到产品级的高度,这里只是为大家提供思路;
首先,我们再次明确一下,WebSocket赋予了我们在浏览器端和服务器进行双向通信的能力,这样,我们可以实时的将数据发送给服务器,然后再广播给所有的客户端。这和聊天程序的思路是一致的。
接下来,服务器端的代码不用做任何修改,在html页面中准备一个canvas,作为我们的画布。如何在canvas上用鼠标画图形呢?我们需要监听mousedown、mousemove和mouseup三个鼠标事件。说到这里,大家应该知道怎么做了。没错,就是在按下鼠标的时候,记录当前的坐标,移动鼠标的时候,把坐标发送给服务器,再由服务器把坐标数据广播给所有的客户端,这样就可以在所有的客户端上同步绘画了;最后,mouseup的时候,做一些清理工作就ok了。下面是一些伪代码:
查看源代码
打印帮助

01var WhiteBoard = function( socket, canvasId ){

02                var lastPoint = null,

03                    mouseDown = false,

04                    canvas = getById(canvasId),

05                    ctx = canvas.getContext('2d');

06  

07                var handleMouseDown = function(event) {

08                    mouseDown = true;

09                    lastPoint = resolveMousePosition.bind( canvas, event )();

10                };

11  

12                var handleMouseUp = function(event) {

13                    mouseDown = false;

14                    lastPoint = null;

15                };

16  

17                var handleMouseMove = function(event) {

18                    if (!mouseDown) { return; }

19                    var currentPoint = resolveMousePosition.bind( canvas, event )();

20                    socket.send(JSON.stringify({

21                        event: 'draw',

22                        data: {

23                            points: [

24                                lastPoint.x,

25                                lastPoint.y,

26                                currentPoint.x,

27                                currentPoint.y

28                            ]

29                        }

30                    }));

31  

32                    lastPoint = currentPoint;

33                };           

34  

35                var init = function(){

36                    addEvent( canvas, 'mousedown', handleMouseDown );

37                    addEvent( canvas, 'mouseup', handleMouseUp );

38                    addEvent( canvas, 'mousemove', handleMouseMove );

39  

40                    var img = new Image();

41                    addEvent( img, 'load', function(e){

42                        canvas.width = img.width;

43                        canvas.height = img.height;

44                        ctx.drawImage( img, 0, 0 );

45                    } );

46                    img.src = '/img/diablo3.png';

47                };

48  

49                var drawLine = function(data) {

50                    var points = data.points;

51                    ctx.strokeStyle = 'rgb(255, 15, 255)';

52                    ctx.beginPath();

53                    ctx.moveTo( points[0] + 0.5, points[1] + 0.5 );

54                    ctx.lineTo( points[2] + 0.5, points[3] + 0.5 );

55                    ctx.stroke();

56                };

57  

58                function resolveMousePosition(event) {

59                    var x, y;

60                    if (event.offsetX) {

61                        x = event.offsetX;

62                        y = event.offsetY;

63                    } else {  //(注意)实际开发中,这样获取鼠标相对canvas的坐标是不对的

64                        x = event.layerX - this.offsetLeft;

65                        y = event.layerY - this.offsetTop;

66                    }

67                    return { x: x, y: y };

68                };

69  

70                init();

71  

72                return {

73                    draw : drawLine

74                    //ctx : ctx,

75                    //canvas : canvas

76                }

77            }( 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.

评分

参与人数 1学分 +4 收起 理由
sanjican + 4 技术贴,筒子膜拜了~~

查看全部评分

共收到 7 条回复
sanjican · #2 · 2013-5-15 16:11:22  回复
预热一下~~
magicliang · #3 · 2013-5-15 16:50:37  回复 支持 反对
我们的下一个项目很可能使用到它,先留着。

点评

共同交流  详情 回复 发表于 2013-5-15 18:19
admin · #4 · 2013-5-15 18:19:14  回复 支持 反对
magicliang 发表于 2013-5-15 16:50
我们的下一个项目很可能使用到它,先留着。

共同交流
movemoon · #5 · 2013-5-15 23:18:08  回复 支持 反对
学习啦,找时间试一试LZ给的例子。websocket真的很方便,比长轮询什么的效率高很多呢,如果IE再支持一下就好啦~

点评

IE10及以上支持  详情 回复 发表于 2013-5-16 16:39
admin · #6 · 2013-5-16 16:39:40  回复 支持 反对
movemoon 发表于 2013-5-15 23:18
学习啦,找时间试一试LZ给的例子。websocket真的很方便,比长轮询什么的效率高很多呢,如果IE再支持一下就 ...

IE10及以上支持
ssc · #7 · 2013-5-18 10:01:43  回复 支持 反对
最近正在用 真心赞一个
azxczhuaifei · #8 · 2013-8-17 16:09:06  回复 支持 反对
[Actionscript3] 纯文本查看 复制代码
gf
dfgfdg
df
fd
g
dfg
fg
f

gggg
g
gegetttttttttt
gtgt



tg
tg
回帖
B Color Image Link Quote Code Smilies
Command + Enter
快速回复 返回顶部 返回列表