新兴Web技术杂谈 之 WebSocket

开篇

最近几年随着各大浏览器以及Web标准技术的蓬勃发展, 基于浏览器的B/S应用已经渐渐变得与我们过去所认知的完全不同。
尤其是移动互联设备的兴起以及HTML5的诞生,将Web开发带入了一个全新的时代。
借助各种先进的技术,Web应用有了更好的用户体验 更强大的功能和性能,很多原本只能在桌面原生应用中做到的事情现在也可以在Web中完成,甚至可以完成的更好。同时,这些新兴的Web技术也逐渐的模糊了C/S和B/S的界线,很好的结合了两者的优点,摒弃其缺点。
也许在未来 一个Web 便可以承载起软件开发者共同的梦想。
(为什么Web2.0的热浪席卷全球? 为什么google要力推ChromeOS,为什么惠普要大力发展WebOS,为什么SaaS大行其道… )


如果说 Web是计算机应用的未来, 那么以HTML5和JS为代表的标准技术则是Web的未来.

在接下来的若干篇文章中(也可能仅此一篇…囧),我打算向大家介绍一些新兴的Web技术,其中包括HTML5/CSS3/"全新的"JavaScript(为什么叫“全新的”???)/无线互联技术。

当然 由于时间和能力有限 不能向大家做全面透测的讲解, 而且我也没必要做这种细致的 教学性质的讲解,因为网络上优秀的相关的技术资料已经多得不能更多了。

我希望这些文章能够帮助对Web开发不感兴趣或者不甚了解的朋友开阔一下的视野,同时能够从一个相对独特的有趣的视角来向大家展示这些技术。
并且希望在文章中能够传达出我个人的一些思考。


好了 废话先说道这里,下面开始进入正题吧。


WebSocket简介

这次想向大家介绍的是 HTML5中引入的一个非常诱人的新特性 : WebSocket 。

  在很久很久以前, 一个叫做Ajax的东西曾经给Web开发带来了一场革命。 Ajax的核心技术就是一个叫做 XMLHttpRequest的异步传输组件。

利用该组件 我们可以在不刷新页面 不提交Form的前提下,利用JS与服务端进行数据的交互,交互过程对于客户端来说是异步无阻塞的。大致过程是:客户端利用XMLHttpRequest向服务端发送一个异步请求(客户端向服务端传递数据),等待服务端响应,服务端接受到请求后做一些处理然后返回响应(服务端向客户端传递数据),客户端接收到数据后与服务器断开连接,交互结束。这种交互依然是基于标准的HTTP协议,是一种无状态的 响应式的短连接。
  而WebSocket的出现,则可以看作是XMLHttpRequest的升级加强版,它除了也具备“在不刷新页面 不提交Form的前提下,利用JS与服务端进行数据的交互”的特性外,还具备了“有状态,高速,双向互通,持续长连接”等特点。


WebSocket是一种全双工的双向通信技术,主要目的是在web浏览器和web服务器之间提供一种类似TCP scoket的双向的 持续性的 有状态的通讯方式.
它的出现,使得服务端推送,B/S之间的长连接成为了可能。有了WebSocket,在Web页面中利用JS+HTML5等标准技术(而无需Flash ActiveX SilverLight等非标准技术)来实现复杂的 高实时性 高交互性的网络应用不再是天方夜谭(例如游戏股票行情 实时监控数据 远程桌面等等)。


Websocket 由两部分组成,一部分是浏览器中的WebSocket API(隶属于HTML5规范), 由W3C组织制订;一部分是WebSocket协议(可以用任何语言来实现), 由IETF组织制订。
虽然WebSocket 协议看起来更接近TCP scoket协议,但它的定位却是HTTP 1.1的一个升级,因此具备了HTTP协议的很多优点。例如强大的穿墙能力,兼容HTTP反向代理等等。

客户端通过WebSocket与服务端进行通讯时,只有第一握手时交互的信息比较复杂,在握手成功之后的便进入双向长连接的数据传输阶段,此时传输的几乎只是存数据,性能很高。WebSocket的数据传输是基于帧的方式: 0x00 表示数据开始, 0xff表示数据结束,数据以utf-8编码.
(目前 浏览器与服务端之只能传递文本,不能传递二进制。WebSocket协议本身是支持二进制的,只是浏览器中的脚本语言暂时不具备二进制数据处理能力。
)

目前支持WebSocket的浏览器有 FireFox 4.0+ ,chrome 4.0+ ,safari 5.0+ 。

而WebSocket Server的实现则多种多样,几乎每一种流行的服务端编程语言都能找到很多的实现。
Java中比较出色的实现是 JBoss netty , jetty 7+


WebSocket 客户端与服务端通讯的API非常简单, 在浏览器中 使用js来调用:


{code}
//
链接到 WebSocket Server 192.168.0.52:8000
var ws = new WebSocket('ws://192.168.0.52:8000/');

// 向服务端发送信息 :
Hello World
 ws.send('Hello World')
 
 // 一个事件,当接收到服务端发来的消息时触发
.
 ws.onmessage = function(event) {
    //event.data的值就是服务端发送来的数据
.
    var data=event.data;
   
 }
 
// (注意 以上操作全部是异步的)
.
 {code}
 
 
 一个示例

 
 
关于WebSocket的更多细节大家可以去网上找到很多, 在这里我不再详述。 下面我主要通过一个示例,来展示一下 WebSocket为web应用所带来的全新体验。
   网上关于WebSocket的示例绝大多数都是聊天室。过去在web中实现聊天室使用的往往是轮询的方式,即:每隔一段时间去从服务端拉一些数据过来。虽然有comet bigpipe等技术,但是其本质仍然是“拉” 而非服务端“推送”。 有了WebSocket,服务端推送就很好实现了。
   既然网上聊天室的例子已经很多了, 我自然不会再拿聊天室做例子。下面我来说一说这个示例所要实现的效果。
  
  
我们先来设想一个网络游戏中的普通场景: 你登录游戏后,控制你的人物来到游戏世界中的某个广场。你可以在电脑屏幕上看到哪些玩家和你处在同一个广场,并且可以看到其他玩家的行动,而其他玩家也能看到你的行动。
   把这个场景做一个大大的简化,就是这个示例所要演示的内容了。  
   
 示例演示内容:

  
  
用支持WebSocket的浏览器(建议Chrome 6 或 safari 5)访问服务器上的 gtest.html页面, 输入自己的昵称,点击“加入”按钮。
 之后可以在浏览器窗口中看到一个写有自己名字的蓝色方块(这就是你控制的人物),此时通过 W A S D键 可以控制这个方块四处移动(需关闭输入法)。
   而此时 如果别人也来访问这个页面并且也“加入”,则你可以在自己的浏览器上看到“其他人”(其他蓝色方块),而且如果此时其他人移动他们自己的方块,你也能够实时的看到,当然其他人也能看到你。
  
  
这里面与WebSocket相关的关键技术就是同步所有用户方块的坐标。任何一个用户的方块坐标发生变化时,都通知服务端,服务端拿到数据后,广播给所有通过WebSocket连接的浏览器, 浏览器拿到广播的数据后 更新页面显示的内容。
   当然该示例存在很多问题(例如服务端也应该维护一份各个用户的状态信息,并且在必要的时候同步给各个客户端),并不是一个完备的有实际意义的WebSocket应用,不过从中依然可以对WebSocket的强大有一定程度的了解。
   运行效果见下图:
  
  
  
 
客户端js代码 (部分细节见注释):
{code}

    //WebSocket
对象
    var ws = null;
    //
所有用户集合
    var players=null;
   
    //
当前用户(自己)
    var player=null;
   
    //
加入
    function start(){
        //
初始化
        players={};
        player={
            id:null,
            name: document.getElementById('player_name').value,
            x: getRandom(50,500),
            y: getRandom(50,300),
            speed:5,
       
            color:'#ddeeff'
        }
   
        connWS();
    }
   
    //
离开
    function end(){
        window.location.reload();
    }
   
   
    //
初始化并连接到WebSocket服务器
    function connWS(){
       
        //
连接
        ws = new WebSocket('ws://192.168.0.52:8000/');
   
        //
当接收到服务端的数据时
        // 服务端传回的数据有三种格式:
        // Connection: id    该id的用户加入 (只发送给自己,目的是拿到服务端生成的id)
        // Disconnected: id  该id的用户离开
        // <id> {x : 123, y:300, name : "Tom"}  该id的用户的状态(坐标和昵称)
        ws.onmessage = function(e) {
            var data=e.data;
            var isNew=false;
            if (data.indexOf('Connection: ')==0){
                isNew=true;        
                var id=data.substring('Connection: '.length);
                player.id=id;
                ws.send(JSON.stringify(player));
                run();
            }else if(data.indexOf('Disconnected: ')==0){
                var id=data.substring('Disconnected: '.length);
                delete players[id];
                removePlayer(id);
               
            }else if (data.indexOf('<')==0){
                var idx=data.indexOf('>');
                var id=data.substring(1,idx);
                var msg=JSON.parse(data.substring(idx+1));
                players[msg.id]=msg;
            }  
   
        };
       
    }
   
    //
更新指定用户状态
    function updatePlayer(id,msg){
        var div=document.getElementById('p_'+id);
        if (!div){
            div=document.createElement('div');
            div.className='player';
   
            div.id='p_'+id;
            document.body.appendChild(div);
        }  
        div.style.backgroundColor=msg.color;
        div.innerHTML=msg.name;
        div.style.left=msg.x;
        div.style.top=msg.y;
    }
    //
移除指定用户
    function removePlayer(id){
        delete players[id];
        var div =document.getElementById('p_'+id);
        if (div){
            document.body.removeChild(div);
        }  
    }
   
    /////////////////////
    /////////////////////
   
    //
主循环, 每30毫秒更新一下自己的坐标并更新页面显示内容。
    function run(){
        update();
        draw();
        setTimeout( run , 30);
    }
   
    //
更新自己的坐标
    function update(){
        //  W: 87,  A: 65, S: 83,   D: 68,
        var W=window.KEY[87],
            A=window.KEY[65],
            S=window.KEY[83],
            D=window.KEY[68]
       
        if (W && S || !W && !S){
           
        }else if (W){
            player.y-= player.speed;   
        }else if (S){
            player.y+=player.speed;
        }
       
        if (A && D || !A && !D){
           
        }else if (A){
            player.x-=player.speed;
        }else if (D){
            player.x+=player.speed;
        }
        //
把自己的坐标发送给服务器
        ws.send(JSON.stringify(player));
    }
   
    //
更新页面显示内容
    function draw(){
        for (var id in players){
            //console.log(id)
            updatePlayer(id,players[id])   
        }
    }
   
   
    //
刷新页面之前 断开与WebSocket服务器的连接
    window.addEventListener('unload',function(e){
        ws.close();
    }, true);
   
    //
取得指定区间内的随机正整数数
    function getRandom(lower,higher){

            lower=lower||0;

            higher=higher||9999;

            return  Math.floor((higher - lower + 1) * Math.random()) + lower;

   

        }
   
    //
页面初始化
    function init(){
   
    //
键盘事件管理
   
        window.KEY={};
       
        window.addEventListener('keydown',function(e){
            window.KEY[e.keyCode] = true;
        }, true);
       
        window.addEventListener('keyup',function(e){
            window.KEY[e.keyCode] = false;
        }, true);
   
        var id= getRandom(10,99999);
        document.getElementById('player_name').value="test"+id;
       
    }

{code}
 
 
服务端js代码 (服务端也使用js所写) :
{code}
    var sys = require("sys"),
        ws = require("./ws");
   
    //
创建WebSocket Server
    var server = ws.createServer();
   
    //当有客户端接入时

    server.addListener("connection", function(conn){
      //
向接入的客户端发送信息
      conn.send("Connection: "+conn.id);
      //
当收到客户端发送的信息时
      conn.addListener("message", function(message){
        //
向所有客户端发送信息.
        server.broadcast("<"+conn.id+"> "+message);
      });
    });
    //当有客户端断开连接时

    server.addListener("close", function(conn){
      //
向所有客户端发送信息.
      server.broadcast("Disconnected: "+conn.id);
    });
   
    //启动服务 监听8000端口

    server.listen(8000);
{code}
 
服务端基于一个国外网友写的websocket server组件所写.贴出来的部分代码并不能直接运行,需要相关的环境支持.
 大家只要看一下思路就好 其实挺简单的.基于java语言也可以很方便的实现
,
 
 可能会有朋友疑惑,JS怎么能写socket server? 其实如今JS已经进入了一个全新的阶段(这就是文章前面我为什么要说“全新的”JS)。

 要想对这个所谓的“全新的”JS有更进一步的了解,敬请期待后续文章(如果还有的话)。

你可能感兴趣的:(新兴Web技术杂谈 之 WebSocket)