基于jquery mobile+websocket+protocol buffer的IM开发

第一次研究有关web端的框架,结合工作需要,决定搞一个基于websocket+protobuffer的聊天demo。闲话少叙,上干货!

关于jquery mobile
jQuery Mobile 是用于创建移动 Web 应用的前端开发框架。
jQuery Mobile 可以应用于智能手机与平板电脑。
jQuery Mobile 使用 HTML5 & CSS3 最小的脚本来布局网页。
jquery mobile官网 

作为移动web前端开发框架,是否有比jquery mobile更合适的框架,请各路大神指点!

关于websocket
WebSocket protocol 是HTML5的协议。它实现了浏览器与服务器全双工通信(full-duplex)。
关于websocket相关的资料网上有很多,在此不做过多叙述。

基于netty的websocket开发,请参见 之前的博客

关于protocol buffer
protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台。google 提供了三种语言的实现:java、c++ 和 python,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。

基于protocol buffer的开发,js和java都有相关开源的库可以直接调用,下文将详述。

--------------------------------华丽分割线--------------------------------

以下是代码部分!
前端
一、websoocket链接服务器
ws = new WebSocket(url);
	ws.onopen = function(evt) {
        onOpen(evt); 
    }; 
    ws.onclose = function(evt) { 
        onClose(evt) 
    }; 
    ws.onmessage = function(evt) { 
        onMessage(evt) 
    }; 
    ws.onerror = function(evt) { 
        onError(evt) 
    };
    ws.binaryType = "arraybuffer";


这里要注意的是 ws.binaryType = "arraybuffer";也就是说这里采用的是arraybuffer方式与服务器端通信。如果不填写通信方式,返回的数据类型是Blob,解析时会很麻烦。

二、protocol buffer消息构建与发送
js提供了开源的protobuf类库 protobuf.min.js 可以直接使用。
由于使用arrayBuffer方式,还需要引用bytebuffer.min.js、ByteArray.js、long.min.js几个类库。当然基础的jquey库也是不可少的(附件中提供下载)。

首先我们需要准备proto文件(即protocol buffer通信的协议文件)

message RequestMessage{
	required RequestType requestType=1[default = CLIENT_TO_LOGIN];
	enum RequestType {
		CLIENT_REGISTER=0;
		CLIENT_TO_LOGIN=1;
		CLIENT_TO_GAME=2;
		CLIENT_TO_IMSERVER=3;
	}
	required string gameId=2;
	required string serverId=3;
	required string ticket=4;
	optional Command message=5;
}

message Command{
	required int32 commandId=1[default=-1];
	optional bytes message=2;
}

required  必填字段
optional  选填字段
repeated  可重复字段
enum      枚举

这里需要注意的是,java服务器端支持package路径,而js客户端解析proto文件时不要带package路径。

// 消息发送
function sendRequest(command) {
	if (typeof dcodeIO === 'undefined' || !dcodeIO.ProtoBuf) {
		throw (new Error(
				"ProtoBuf.js is not present. Please see www/index.html for manual setup instructions."));
	}
	if (ws == null) {
		throw (new Error("sendMessage error !off_line"));
	}

	var ProtoBuf = dcodeIO.ProtoBuf;
	if (RequestMessage == null) {
		RequestMessage = ProtoBuf.loadProtoFile("protoBuf/Message.proto")
				.build("RequestMessage");
	}
	var obj;
	if (command != null) {
		obj = {
			requestType : 0,
			gameId : gameId,
			serverId : serverId,
			ticket : registerValue,
			message : command
		};
	} else {
		obj = {
			requestType : 0,
			gameId : gameId,
			serverId : serverId,
			ticket : registerValue
		};
	}
	var request = new RequestMessage(obj);
	var msgArray = request.toArrayBuffer();

	var content = new ByteArray(msgArray);
	var buffer = new ByteArray();
	buffer.writeInt(msgArray.byteLength);
	buffer.writeBytes(content);
	ws.send(buffer.data);
}

//消息构建
function createCommand(commandId, sendMsg) {
	if (typeof dcodeIO === 'undefined' || !dcodeIO.ProtoBuf) {
		throw (new Error(
				"ProtoBuf.js is not present. Please see www/index.html for manual setup instructions."));
	}
	if (ws == null) {
		throw (new Error("sendMessage error !off_line"));
	}
	var ProtoBuf = dcodeIO.ProtoBuf;
	var Message = ProtoBuf.loadProtoFile("protoBuf/CommandMessage.proto")
			.build("request_" + commandId);
	var request_obj;
	var request_data;
	switch (commandId) {
	case 12000:
		request_obj = {
			groupId : groupId,
			fromuid : registerValue,
			message : sendMsg,
			msgType : 0,
			nickname : nickName,
			faceIndex : 11
		};
		request_data = new Message({
			message : obj
		});
		break;
	case 10000:

		break;
	}
	if (CommandMessage == null) {
		CommandMessage = ProtoBuf
				.loadProtoFile("protoBuf/CommandMessage.proto")
				.build("Commond");
	}
	var obj = {
		commandId : commandId,
		message : request_data.toArrayBuffer()
	};
	var command = new CommandMessage(obj);
	return command;
}

1、我们使用的消息格式是
requestMessage{
               command{
                         request_command{
                            ……
                         }
               }
}

2、构建protocol buffer对象时,可以先构建key:value键值对{key1:value1,key2:value2},构建对象时将值传入即可。
3、这里为了方式socket通信是黏包,在发送的arrayBuffer前加入了一个int的包长度。

返回消息的处理
// 返回消息处理
function parseResponseMsg(receiveMsg) {
	var command = response(receiveMsg);
	var commandID = command.commandId;
	var ProtoBuf = dcodeIO.ProtoBuf;
	var message = ProtoBuf.loadProtoFile("protoBuf/CommandMessage.proto")
			.build("response_" + commandID);
	
	switch(commandID){
	case sendMsg_protocolID:
		var data = message.decode(command.message);
		if (data.result == response_statue_ok) {
			console.log("发送广播消息成功");
		} else {
			console.log("发送广播消息失败");
		}
		break;
	case getWorldMsg_protocolID:
		var data = message.decode(command.message);
		addMsgItemOnScreen(data.message);
		console.log("收到其他广播消息:" + data.message.message);
		break;	
	}

}

// 消息响应
function response(receiveMsg) {
	var tempBuffer = new ByteArray();
	tempBuffer._writeUint8Array(new Uint8Array(receiveMsg));
	tempBuffer.position = 0;
	var msgBuffer = new ByteArray();
	tempBuffer.readBytes(msgBuffer, 4);
	var ProtoBuf = dcodeIO.ProtoBuf;
	var CommandMessage = ProtoBuf
			.loadProtoFile("protoBuf/CommandMessage.proto").build("Commond");
	var command = CommandMessage.decode(msgBuffer.buffer);
	return command;
}

1、返回的消息仍然在首位添加了包长度字段
2、返回的消息格式是
            command{
                  response_command{
                    ……
                   }
             }


三、jquery mobile的使用
1、开发移动web页面一定要注意添加meta头
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport"
	content="width=device-width, initial-scale=1, user-scalable=yes" />


viewport的content相关解释
width
Width of the viewport in pixels (or device-width). If width isn’t set, it defaults to a desktop size (980px on mobile Safari).

height
Height of the viewport in pixels (or device-height). Generally you don’t need to worry about setting this property.
initial-scale
(0 to 10.0) Multiplier that sets the scale of the page after its initial display. Safe bet: if you need to set it, set it to 1.0. Larger values = zoomed in, smaller values = zoomed out
minimum-scale
(0 to 10.0) The minimum multiplier the user can “zoom out” to. Defaults to 0.25 on mobile Safari.
maximum-scale
(0 to 10.0) The minimum multiplier the user can “zoom in” to. Defaults to 1.6 on mobile Safari.
user-scalable
(yes/no) Whether to allow a user from scaling in/out (zooming in/out). Default to “yes” on mobile Safari.

2、jquery mobile使用过程中主要遇到个问题,跳转到新页面后js脚本未初始化(jquery mobile内部使用ajax方式请求,脚本放在head中不会被加载),我的解决方法是将js脚本放到<div data-role="page">内。当然还有其他方式,比如在a标签中设置 data-ajax=“false”属性,看自己的需求喽!
3、由于使用jquery mobile只用了一些皮毛,所以遇到的问题并不多,具体的使用细节可以参考官网,推荐一篇 文库文章

四、服务器端开发
由于我们服务器端的架构相对复杂,这里不做具体阐述,有兴趣的同学可以看我 之前的博客
需要注意的是在eclipse环境下需要安装protocol buffer的插件,很不幸的是在我天朝上国安装这个插件是相对麻烦的 http://protobuf-dt.googlecode.com/git/update-site,因为……
不过苦日子马上就过去了!!!          

你可能感兴趣的:(jquery,netty,websocket,mobile,protocol,buffer)