您的位置:首页 > 移动开发 > Unity3D

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、有一个猪一样的老板兼上司,不愁项目不死,是时候找下家了
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: