如题,项目背景:手机端H5需要实时控制PC端参与游戏互动。
服务端用java语言编写,为手机端和PC端消息通信做中间媒介,实时和两端保持长连接通信。通信消息用google开发的protobuf对数据结构进行序列化和反序列化。
手机端H5:
1、js引入第三方protobuf第三方js库,。
2、制定通信协议和字段类型,字段名。把它放入一个消息文件中,便于protobuf解析:部分代码如下:
package message;
syntax = "proto3";
message send{
int32 pid = 1;
bytes data = 2;
}
message receive{
int32 pid=1;
int64 time=2;
bytes data=3;
}
3、自行封装消息解析和发送。消息发送: var message = resolver.create(data);
var databuff = resolver.encode(message).finish();
var msg = {
pid: Number(pid),
data: databuff
}
var msg = webSocketProtobuf.send.create(msg);
var buff = webSocketProtobuf.send.encode(msg).finish();
if (webSocketProtobuf.socket.readyState === 1) {
webSocketProtobuf.socket.send(buff);
} else {
if (errorCB)
errorCB();
}
消息解析:socket.onmessage = function (evt) {
if (typeof (evt.data) == "string") {
console.log(evt.data);
} else {
var reader = new FileReader();
reader.readAsArrayBuffer(evt.data);
reader.onload = function (e) {
var buf = new Uint8Array(reader.result);
var de = webSocketProtobuf.receive.decode(buf);
if (onMessage) {
onMessage(de);
}
reader.onload = null;
}
}
}
4、H5主要调用代码如下:初始化通信: webSocketProtobuf.setWebSocket(wsUrl,OnError,OnOpen,OnMessage);
消息接收根据事先定义好的协议号进行区分以及后续的逻辑处理:
function OnMessage(msg) {
switch(msg.pid){
case 3:
//倒计时等待
if(loadProgress!=100 && !unfinish){
InitData();
}
let serverTime = webSocketProtobuf.receivetime.decode(msg.data);
ShowCountTime(serverTime.time);
break;
}
}
消息发送接口:
function SendMessage(pid,data) {
if(webSocketProtobuf.socket == null && !isOver){
if (null != wsUrl) {
webSocketProtobuf.setWebSocket(wsUrl,OnError,OnOpen,OnMessage);
}
return;
}
if(data.length != 0){
webSocketProtobuf.sendData(pid,data,null);
}
}
5、加一个守护线程定时发送心跳协议,维护长连接。
至此移动端暂告一段落。
PC端:
unity websocket这边我用的BestHttp插件。
1、导入BestHttp插件,用于websocket通信。
2、导入protobuf的dll动态库, 用于后续的消息解析和序列化。官网下载的时候会有一个C#的消息生成bat文件,首先创建一个文件,文件内容包括和服务器通信的消息协议,内容如下:
message basemessage{
required int32 pid=1;
required bytes data=2;
}
message receivemessage{
required int32 pid=1;
required int64 time=2;
required bytes data=3;
}
点击生成对应的c#脚本,注意文件的路径。点击生成dll直接导入直接代替刚才生成的.cs文件,还是很方便的。
3、接下来就是unity接收消息和发送消息:
//连接服务器
public void ConnectWebSocket( string webUrl, string screenId, System.Action
{
if( null != webSocket )
{
CloseWebsocket();
}
if( null != receiveCallBack )
{
callBack = receiveCallBack;
}
webAddress = string.Format( webAddress, webUrl, screenId );
webSocket = new WebSocket( new System.Uri( webAddress ) );
webSocket.StartPingThread = true;
if( HTTPManager.Proxy != null )
{
webSocket.InternalRequest.Proxy = new HTTPProxy( HTTPManager.Proxy.Address, HTTPManager.Proxy.Credentials, false );
}
webSocket.OnOpen += OnOpen;
webSocket.OnBinary += OnBinaryReceived;
webSocket.OnClosed += OnClosed;
webSocket.OnError += OnError;
webSocket.Open();
}
消息接收解析:
void OnBinaryReceived( WebSocket ws, byte[] byteArr )
{
ProtoMessage.receivemessage receivemessage = DeserializeProto( byteArr, typeof( ProtoMessage.receivemessage ) ) as ProtoMessage.receivemessage;
ProtoBuf.IExtensible extensible = null;
Debug.LogError( receivemessage.pid );
switch( receivemessage.pid )
{
case 2:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.receiveuserinfo ) ) as ProtoMessage.receiveuserinfo;
break;
case 4:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.receiveplayer ) ) as ProtoMessage.receiveplayer;
break;
case 11:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.receivequit ) ) as ProtoMessage.receivequit;
break;
case 5:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.receiverole ) ) as ProtoMessage.receiverole;
break;
case 12:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.receiveitem ) ) as ProtoMessage.receiveitem;
break;
case 10:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.receivespeed ) ) as ProtoMessage.receivespeed;
break;
case 8:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.reveivegameover ) ) as ProtoMessage.reveivegameover;
break;
case 7:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.receivegetitem ) ) as ProtoMessage.receivegetitem;
break;
case 9:
extensible = DeserializeProto( receivemessage.data, typeof( ProtoMessage.heart ) ) as ProtoMessage.heart;
OnHandleHeart( extensible );
return;
}
if( null != callBack )
{
callBack( receivemessage.pid, extensible );
}
}
反序列化消息内容:
//DeserializeProto
private ProtoBuf.IExtensible DeserializeProto( byte[] buffer, Type type )
{
ProtoBuf.IExtensible proto = null;
using( System.IO.MemoryStream ms = new System.IO.MemoryStream() )
{
ms.Write( buffer, 0, buffer.Length );
ms.Seek( 0, System.IO.SeekOrigin.Begin );
proto = serializer.Deserialize( ms, null, type ) as ProtoBuf.IExtensible;
ms.Close();
}
return proto;
}
向服务端发送消息:
//send bytearr message
public void SendMessage( int pid, ProtoBuf.IExtensible proto )
{
byte[] buffer = SerializeProto( proto );
ProtoMessage.basemessage basemessage = new ProtoMessage.basemessage();
basemessage.pid = pid;
basemessage.data = buffer;
byte[] sendBuff = SerializeProto( basemessage );
if( null != webSocket )
{
webSocket.Send( sendBuff );
}
}
序列化消息:
//serialze the protot
byte[] SerializeProto( ProtoBuf.IExtensible proto )
{
byte[] protoBuff = null;
using( System.IO.MemoryStream ms = new System.IO.MemoryStream() )
{
serializer.Serialize( ms, proto );
if( ms.Length > 0 )
{
protoBuff = new byte[(int)ms.Length];
ms.Seek( 0, System.IO.SeekOrigin.Begin );
ms.Read( protoBuff, 0, protoBuff.Length );
}
ms.Close();
}
return protoBuff;
}
4、和移动端一样创建一个守护线程,发送心跳协议:
//守护线程
private void HeartThread()
{
while( true )
{
long curTime = Global.GetTimeStamps();
if( curTime - lastTime >= 5000 )
{
lock( connectObj )
{
SendHeartMessage();
}
}
if( !IsConnecting )
{
break;
}
Thread.Sleep( 1000 );
}
}
以上是用到的主要的代码,并不是全部代码,如需请留言探讨。