`

one Web one Dream ------ 新兴Web技术杂谈 之 WebSocket

阅读更多


发一篇前一阵在公司内部写的关于HTML5的文章.

文章很水,没啥技术含量,主要是向同事普及一下HTML5的知识.

=========================

one Web one Dream ------ 新兴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来调用:

// 链接到 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;
}

// (注意 以上操作全部是异步的).

 

一个可多人互动的示例

     关于WebSocket的更多细节 大家可以去网上找到很多, 在这里我不再详述。 下面我主要通过一个示例,来展示一下 WebSocket为web应用所带来的全新体验。

    网上关于WebSocket的示例绝大多数都是聊天室。过去在web中实现聊天室使用的往往是轮询的方式,即:每隔一段时间去从服务端拉一些数据过来。虽 然有comet bigpipe等技术,但是其本质仍然是"拉" 而非服务端"推送"。 有了WebSocket,服务端推送就很好实现了。既然网上聊天室的例子已经很多了, 我自然不会再拿聊天室做例子。下面我来说一说这个示例所要实现的效果。

     我们先来设想一个网络游戏中的普通场景 : 你登录游戏后,控制你的人物来到游戏世界中的某个广场。你可以在电脑屏幕上看到哪些玩家和你处在同一个广场,并且可以看到其他玩家的行动,而其他玩家也能看到你的行动。
把这个场景做一个大大的简化,就是这个示例所要演示的内容了。

示例演示内容:

    用支持WebSocket的浏览器(建议Chrome 6 或 safari 5)访问服务器上的 gtest.html页面, 输入自己的昵称,点击"加入"按钮。
之后可以在浏览器窗口中看到一个写有自己名字的蓝色方块(这就是你控制的人物), 此时通过 W A S D键 可以控制这个方块四处移动(需关闭输入法)。
而此时 如果别人也来访问这个页面并且也"加入",则你可以在自己的浏览器上看到"其他人"(其他蓝色方块),而且如果此时其他人移动他们自己的方块,你也能够实时的看到,当然其他人也能看到你。

    这里面与WebSocket相关的关键技术就是同步所有用户方块的坐标。任何一个用户的方块坐标发生变化时,都通知服务端,服务端拿到数据后,广播给所有通过WebSocket连接的浏览器, 浏览器拿到广播的数据后 更新页面显示的内容。
当然该示例存在很多问题(例如服务端也应该维护一份各个用户的状态信息,并且在必要的时候同步给各个客户端),并不是一个完备的有实际意义的WebSocket应用,不过从中依然可以对WebSocket的强大有一定程度的了解。

运行效果见下图:


 两个用户, 小胖 大胖分别访问该页面, 能够看到彼此的移动。

客户端js代码 (部分细节见注释):

//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.78: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;
	
}



 

服务端js代码 (服务端也使用js所写) :

var sys = require("sys"),
    ws = require("./ws");

//创建WebSocket Server
var server = ws.createServer();

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

//启动服务 监听8000端口
server.listen(8000);

服务端基于一个国外网友写的websocket server组件所写.贴出来的部分代码并不能直接运行,需要相关的环境支持.

大家只要看一下思路就好 其实挺简单的.基于java语言也可以很方便的实现,

可能会有朋友疑惑,JS怎么能写socket server? 其实如今JS已经进入了一个全新的阶段(这就是文章前面我为什么要说"全新的"JS---其实也没啥 就是SSJS而已)。
要想对这个所谓的"全新的"JS有更进一步的了解,敬请期待后续文章(如果还有的话)。

 

下面是本文示例的演示网址,请使用Chrome 5+ 或者 Safari 5+ 访问. 这个示例要多人一起访问才有意思 . (代码见附件):

(略)

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics