unity客户端与java服务器利用thrift通信初试
2015-04-13 13:46
489 查看
背景
公司做的是手游,用的是unity客户端语言为c#,服务器为java,客户端已基本做完,服务器刚招的人第一天
一开始服务器给了个ip和port,我就开始连接,我直接用原生socket直接socket.Send(byte[] a),发现不管怎么发对方都接受不到,于是开始想不会它那边没开吧,于是ping ip,发现有数据包说明是通的,可是怎么判断判断给我的端口是否可用,在同事的帮助下,在控制面板中的打开或关闭Windows功能中打开telnet客户端,于是telnet ip port,有返回值,奇怪了。在接下来的一天中我试了在我代码中找问题,可是一无所获,接下来放清明了。
第二天
一开始老板开会说我这里要抓紧,赶紧弄,给我四天接好,心中呵呵。服务器端说他那边一切正常,用模拟的可以跑通,我是在无语了,接下来他说他那边接收到是个NetMessage消息体,而且只能接受这个。然后我去看了消息体
[Serializable] public class NetMessage { private IMessageType messageType; private byte[] body; public IMessageType getMessageType() { return messageType; } public void setMessageType(IMessageType messageType) { this.messageType = messageType; } public byte[] getBody() { return body; } public void setBody(byte[] body) { this.body = body; } }
我现在要发送一个类过去,这个类继承自thrift中的TBase,这个不用看,它是thrift自动生成的
public partial class CS_Login : TBase { private string _userName; private string _password; private LoginType _loginType; public string UserName { get { return _userName; } set { __isset.userName = true; this._userName = value; } } public string Password { get { return _password; } set { __isset.password = true; this._password = value; } } /// <summary> /// /// <seealso cref="LoginType"/> /// </summary> public LoginType LoginType { get { return _loginType; } set { __isset.loginType = true; this._loginType = value; } } public Isset __isset; #if !SILVERLIGHT [Serializable] #endif public struct Isset { public bool userName; public bool password; public bool loginType; } public CS_Login() { } public void Read (TProtocol iprot) { TField field; iprot.ReadStructBegin(); while (true) { field = iprot.ReadFieldBegin(); if (field.Type == TType.Stop) { break; } switch (field.ID) { case 1: if (field.Type == TType.String) { UserName = iprot.ReadString(); } else { TProtocolUtil.Skip(iprot, field.Type); } break; case 2: if (field.Type == TType.String) { Password = iprot.ReadString(); } else { TProtocolUtil.Skip(iprot, field.Type); } break; case 3: if (field.Type == TType.I32) { LoginType = (LoginType)iprot.ReadI32(); } else { TProtocolUtil.Skip(iprot, field.Type); } break; default: TProtocolUtil.Skip(iprot, field.Type); break; } iprot.ReadFieldEnd(); } iprot.ReadStructEnd(); } public void Write(TProtocol oprot) { TStruct struc = new TStruct("CS_Login"); oprot.WriteStructBegin(struc); TField field = new TField(); if (UserName != null && __isset.userName) { field.Name = "userName"; field.Type = TType.String; field.ID = 1; oprot.WriteFieldBegin(field); oprot.WriteString(UserName); oprot.WriteFieldEnd(); } if (Password != null && __isset.password) { field.Name = "password"; field.Type = TType.String; field.ID = 2; oprot.WriteFieldBegin(field); oprot.WriteString(Password); oprot.WriteFieldEnd(); } if (__isset.loginType) { field.Name = "loginType"; field.Type = TType.I32; field.ID = 3; oprot.WriteFieldBegin(field); oprot.WriteI32((int)LoginType); oprot.WriteFieldEnd(); } oprot.WriteFieldStop(); oprot.WriteStructEnd(); } public override string ToString() { StringBuilder sb = new StringBuilder("CS_Login("); sb.Append("UserName: "); sb.Append(UserName); sb.Append(",Password: "); sb.Append(Password); sb.Append(",LoginType: "); sb.Append(LoginType); sb.Append(")"); return sb.ToString(); } }
首先要把这个类序列化为byte[]类型,我请求看看服务器端的序列化工具,得到的回答是apache.thrift.TSerializer.serialize(TBase base),我问有c#客户端的没有,说没有,我去网上查,得知thrift是facebook开发的可伸缩的跨语言服务开发框架,我查啊查,找到dem0(http://www.cnblogs.com/liping13599168/archive/2011/09/15/2176836.html),可是缺少dll,我自己下了个工程把里面的dll导出到我的项目中,发送过去对方还是接受不到,我没办法了。
第三天
我又开始怀疑服务器了,于是他另开了个服务器,这次可以接收到的是byte[],我试着用原生看的socket发,也没有收到值,疯了,看了网上有关java与c#通信的注意事项后,在所发字符串后加了“/n”换行符,这次终于接受到了,总之这次是我搞过做蛋疼的事了。那么问题来了,之前的那台服务器也是因为同样的问题么,试过之后还是不行。接下来我受不了服务器了,我要求服务器告诉我,你是怎么把NetMessage发出去的,于是他去翻看框架的东东了,我已经不想搞了,菜鸟对菜鸟,连你妹啊,说好的有人搭框架的,说好你妹的。
接下来服务器告诉我,要先发NetMessage的为int类型的type,再发body,我和他商量觉得我先来接受他的东东,我只要能解出来,在反着来就ok,
这次我从字节开始读,我不信还不行,于是
byte[] bytes = new byte[4096]; int rev = socket.Receive(bytes); public NetMessage Read(byte[] bytes) { byte[] lengthBytes = SubByteArray(bytes, 0, 4); byte[] headBytes = SubByteArray(bytes, 4, 4); Debug.Log("lengthBytes==" + BitConverter.ToInt32(lengthBytes, 0)); Debug.Log("headBytes==" + BitConverter.ToInt32(headBytes, 0)); int bodyLength = BitConverter.ToInt32(lengthBytes, 0) - 4; byte[] bodyBytes = SubByteArray(bytes, 8, bodyLength); NetMessage netMessage = new NetMessage(); netMessage.setMessageType((IMessageType)BitConverter.ToInt32(headBytes, 0)); netMessage.setBody(bodyBytes); return netMessage; }
起初这个读的方法,解出来的数字不对,于是我打印了每一个byte和服务器的对,发现它那边竟然有负数,我这边的没有,问题就出在这个地方?我查了发现java的int32为有符号的整数,而c#中为无符号整数,于是
if (BitConverter.IsLittleEndian) { Array.Reverse(headBytes); Array.Reverse(lengthBytes); }
搞定,搞定的只是数据头,读到的32,而不在type正常的取值范围,我交抢了,你妈玩我呢,服务器又一次开始看底层这次我和他一起,发现正确的格式应该是length+type+body,这次就解释通了,可我怎么把body解出来呢,反序列话工具怎么搞,我试啊试的又一天结束了。
第四天
我想不就是一个发序列化工具么,我看看能不能把java的jar转为c#的dll,发现网上竟然有可行的办法,我去试,导出的dll,序列化的方法apache.thrift.TSerializer.serialize(TBase base),与我这边c#的TBase竟不是同一个基类,我觉得可能是命名空间的问题,可是仔细一看发现不是,c#中的TBase只是apache.thrift.TBase得子集。这时我不干了,我干不了了,我能力有限,我毕业不到一年,更是缺少底层经验。后来发现根本没人在乎我会不会,我只要做就好了。
我在服务器的指导下去看thrift框架中java是如何序列化和反序列化的,发现要解出来必须依赖于框架中的方法,我自己必须用thrift的通信框架。
我重新换回thrift通讯框架,但是我不知道,怎么解,于是又遇到了之前的问题,那个读的方法是不是服务器那边写好的,自动生成的呢,我再一次去看上面的demo,想照着他的方法,自己写一个读的方法(这时思路就错了,demo中直接对数据读写,而我们这里对数据进行了包装),我试到天黑也没试出来。
第五天
服务器那边理清了,受不了我这边了,开始帮我研究客户端,终于让他写出了读的方法,我读出来了。public class MyTProtocol : TProtocolDecorator { private TProtocol tprotocol; public MyTProtocol(TProtocol tprotocol) : base(tprotocol){ this.tprotocol = tprotocol; } }
TProtocolDecorator是个抽象类,于是自己写个MyTProtocol来调用TProtocolDecorator中的读写方法来操作TProtocol,得到TProtocol中的数据, 看完这个我万分惭愧,自己的东东让服务器的来搞定,于是自己去看thrift框架,看了半天还只是会用而已,只知道了个大概,想想也是人家写的时候肯定时间不短。
该自己序列化了,去研究了对应java中的方法,发现要先得到一个TProtocol把继承子TBase中的各种类线写入 TBase.write(TProtocol),然后再从TProtocol中得到byte[]
public class TSerializer { private TProtocol protocol_; private static MemoryStream InputStream = new MemoryStream(); private static MemoryStream OutputStream = new MemoryStream(); private TStreamTransport transport = new TStreamTransport(InputStream, OutputStream); public TSerializer(){ TBinaryProtocol.Factory factory = new TBinaryProtocol.Factory(); protocol_ = factory.GetProtocol(transport); } public byte[] serialize(TBase mBase){ transport.Open (); byte[] bytes = null; mBase.Write(protocol_); int byteLength = (int)OutputStream.Length; bytes=new byte[byteLength]; OutputStream.Position = 0; int Count= OutputStream.Read(bytes, 0, byteLength); transport.Close (); return bytes; } }
问题还在,服务器无法解析body部分。
第六天
我试了各种方式,还是无法解析,得到的结果是body部分的byte[]有错位,他读到的body前多了个body的长度,其他的都对。怎么找也找不到,终于我的unity每次运行就卡死,不知所踪,后来发现只要一读int i = tpd.ReadI32();就阻塞,有是半天时间找问题,后来发现服务器把原来发给我的数据停掉了,呵呵,终于体会到阻塞的威力。
过了会服务器小哥不干了,说搞不定,辞职了,不到两周时间。
总结
再失败也应有所收获1、java端与c#通信在发送字符串的时候,如果读不到加个“/n”
2、telnet客户端检测远程ip的端口
3、java断与c#通信在发送字符串的时候,注意int类型,两种语言不同
4、很多时候看源码(分析问题)很容易找到解决问题的方式
5、有一个猪一样的老板兼上司,不愁项目不死,是时候找下家了
相关文章推荐
- 使用thrift实现了Java服务器和nodejs客户端之间的跨平台通信
- Java 利用套接字Socket实现简单的服务器与客户端通信
- Java 利用套接字Socket实现简单的服务器与客户端通信
- 转一篇Unity客户端与Java服务器的通信
- java 通过 socket 实现 服务器和客户端的通信 TCP
- 智能点餐系统开发纪实1-----java服务器搭建,android客户端实现通信
- Thrift java服务器与客户端示例
- Java实现服务器和客户端简单通信
- 【Linux网络编程实例】实例二:利用TCP协议进行客户端与服务器通信
- Thrift java服务器与客户端示例 - john c - 博客园
- 利用sockets实现服务器客户端简单通信
- JAVA中Socket服务器/客户端的通信
- Java基于UDP实现服务器和多客户端之间的通信
- Java实现单个客户端与服务器UDP通信
- android客户端与javaweb服务器端数据通信-异步Get
- c++做服务器,java当客户端,采用 tcp协议之相互通信demo
- Java SSL 服务器 客户端通信 试用
- 数据通信之数据转码字节映射加密发送:客户端c++和服务器java
- 一个 Java 的 Socket 服务器和客户端通信的例子
- 一个 Java 的 Socket 服务器和客户端通信的例子