文档章节

C#与Java通过protobuf进行网络通信过程中遇到的问题

a
 andyqingliu
发布于 2017/01/03 17:48
字数 1476
阅读 1102
收藏 11

  上周体验了一把protobuf,google大佬搞的东西据说很多人用,优点自然不用多说,随便搜搜结果一大堆。为了测试这个玩意,随便弄了一个客户端,拿C#写了一个简单的控制台程序请求服务端,服务端拿java的HttpServer做了一个简单的响应客户端请求。

Protobuf用的2.6.1版本。

客户端下载地址:https://github.com/andyqingliu/TestHttpClient.git

服务端下载地址:https://github.com/andyqingliu/TestHttpServer.git

(Note: 写了很多测试代码,为了测试方便,所以代码比较混乱。)

记录一些重要的信息备忘,顺便梳理一下当时的思路和遇到的几个重要的问题及解决方案。

1.客户端数据准备

a.用C#自带的WebClient类异步发送数据

WebClient wc = new WebClient();
wc.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
wc.Encoding = Encoding.UTF8;
wc.UploadDataCompleted += new UploadDataCompletedEventHandler(OnUploadDataCompleted);


wc.UploadDataAsync(uri, "POST", buff);

b.利用MemoryStream类来存放要发送给服务端的字节流。

    原来MemoryStream有ToArray的方法直接把字节流转换为字节数组。刚开始自己写读取流方法一直有问题,写不了数据,后来请教同事发现有现成的方法,绕了一大圈。同样,MemoryStream也有字节数组的构造函数,很是方便。

c.客户端协议如下表.

客户端协议格式

协议第一部分 协议第二部分 协议第三部分
4字节的int,标记协议长度 2字节的short,标记协议个数 N字节的协议内容

其实这里更合理的组织方式是第一部分与第二部分对调。这里仅仅测试,写的比较随意。此表只是为了说清楚协议的组织方式。通过与同事讨论,觉得以如下方式组织协议更为合理。

合理的协议组织方式

协议包头 协议长度 协议内容 协议长度 协议内容
2字节short 4字节int N字节内容 4字节int N字节内容

协议包头定义2字节short来存放协议个数,然后根据协议个数,分别是协议长度和协议内容。

d.Protobuf的C#Api提供了对象的序列化与反序列化方法如下。

ProtoBuf.Serializer.Serialize(stream, T);
ProtoBuf.Serializer.Deserialize<T>(stream);

 

2.服务端数据准备

a.利用HttpServer来创建服务端监听。

final InetSocketAddress sa = new InetSocketAddress(8888);
HttpServer server = null;
try {
	server = HttpServer.create(sa, 0);
	} catch (IOException e) {
		e.printStackTrace();
	}
		server.createContext("/",new MyResponseHandler());
		server.setExecutor(null);
		server.start();

b.通过MyResponseHandler类的handle方法的httpExchange参数的getRequestBody方法来获取客户端的请求的流信息,并进行解析成对应的对象。

c.服务端解析协议方式对应客户端协议格式。

d.Java对Protobuf字节数组处理方式比较蛋疼。

每个协议对象都有一个parseFrom方法来序列化。这一点比较方便,也是蛋疼的地方。

反序列化则是每个对象都会生成一个Protobuf的类型,比较麻烦。

Person.Builder person = Person.newBuilder();
person.setValue(12345);
C2S_GetFriendList_message.Builder build = C2S_GetFriendList_message.newBuilder();
build.setResult(54321);
build.setP(person);
C2S_GetFriendList_message friendList_message = build.build();

 

————————————————————————————————————————————

一段空白之后遇到了传说中在网络传输过程中的大小端问题。个人理解是这样的:网络协议规定低内存地址存放高字节,高内存地址存放低字节。不同处理器处理字节的方式各有不同,X86处理器以小端方式处理字节序列,发送字节数组,即低内存地址存放低字节,高内存地址存放高字节。而java虚拟机则以大端的顺序来存放。即低内存地址存放高字节,高内存地址存放高字节。

举例说明,比如有一个int = 129,其转换为字节数组为{129,0,0,0},一个大小为4的字节数组,假如有一段内存地址,从左到右内存地址值变大,字节顺序是这样:

字节序列

0x0643E690 0x0643E691 0x0643E692 0x0643E693
129 0 0 0

对应的二进制为 00000000 00000000 00000000 10000001 .十进制为129.

 

而对于java虚拟机,则会把上述字节数组翻译为“大端”,其在内存中的字节序列如下:

Java虚拟机内存字节序

0x0643E690 0x0643E691 0x0643E692 0x0643E693
0 0 0 -127

对应的二进制为 10000001 00000000 00000000 00000000. 由于Java没有无符号数,最高位代表符号位,这里的二进制最高位为1,代表负数,而Java虚拟机用补码表示负数,所以此数的绝对值代表的二进制为:01111111 00000000 00000000 00000000.加上符号,转换为十进制为-2130706432。

于是,如果没有对大小端进行统一,就会出现发送方的数据与接收方的数据不一致的问题。发送129,收到的却是-2130706432。

解决办法是:需要在客户端进行转换,可以用 IPAddress.HostToNetworkOrder把需要发送的字节序列转换为网络字节序列,即转换为大端序列。代码如下:

int testInt = 129;
byte[] ints = System.BitConverter.GetBytes(IPAddress.HostToNetworkOrder(testInt));

然后在服务端也进行转换,转换为大端序列,如下:

ByteBuffer bbBuffer = ByteBuffer.wrap(bs);
bbBuffer.order(ByteOrder.BIG_ENDIAN);

这样就能保证发送方与接收方都采用大端的方式,避免得到不想要的结果。
 

可能有些同学会问一个问题,为什么c#发送的字节数组是{129,0,0,0},而到了java端变成了{-127,0,0,0},这是因为c#的byte是无符号的8位字节,而java端的byte是有符号的,对于c#,byte的取值范围是(0,255),而Java端的byte取值范围是(-128,127),129的二进制为10000001 ,对于c#而言,对应的十进制是129,而对于java而言,最高位为1,表示负数,负数用补码来表示,所以其绝对值的二进制为011111111,十进制为127,所以java端认为这个值是-127.

 

© 著作权归作者所有

共有 人打赏支持
a
粉丝 2
博文 1
码字总数 1476
作品 0
北京
私信 提问
Google protobuf与Socket通信数据流

Google protobuf与Socket通信数据流,IM通信数据流; 1. protobuf的使用与集成,protobuf生成Java Proto文件? 2. android使用wire方式生成protobuf的Java文件? Protobuf Plugin for Gradle...

desaco
01/31
0
0
多语言跨平台序列化框架Google Protobuf-with Netty

protoc安装 下载Protobuf [Protobuf][https://code.google.com/p/protobuf/] 我下载的是Protobuf 2.5.0版本. 如果是Windows系统,可直接下载win32, 解压出protoc.exe到任意目录.Linux系统下载...

震秦
2013/08/26
0
1
protobuf,json,xml,binary,Thrift之间的对比

golang 使用 protobuf 的教程 golang使用protobuf 一条消息数据,用protobuf序列化后的大小是json的10分之一,xml格式的20分之一,是二进制序列化的10分之一,总体看来ProtoBuf的优势还是很明...

mickelfeng
2018/11/19
0
0
这么多人晒简历,我也来一个,赶紧喷吧。。。

计算机语言: 熟练: C/C++ (10年) 掌握: Java、 C#(WPF)、Python、 Pascal、awk, scala、F#、Linux Bash Shell等语言,并能用于辅助C/C++,提高工作效率。 C/C++语言库(熟练的掌握) ST...

newzai
2013/09/16
4.6K
38
在网络通讯中应用Protobuf

Protobuf的设计非常适用于在网络通讯中的数据载体,它序列化出来的数据量少再加上以K-V的方式来存储数据,对消息的版本兼容性非常强;还有一个比较大的优点就是有着很多的语言平台支持。下面...

泥水佬
2013/08/28
0
16

没有更多内容

加载失败,请刷新页面

加载更多

指针数组和数组指针的区别

这两个名字不同当然所代表的意思也就不同。我刚开始看到这就吓到了,主要是中文太博大精深了,整这样的简称太专业了,把人都绕晕了。从英文解释或中文全称看就比较容易理解。 指针数组:arr...

天王盖地虎626
32分钟前
1
0
Qt那些事0.0.18

今天要记一下Qt中的Resource。自我感觉理解的不错,但是还会难免有谬误,在日后有可能会更新,也有可能不会。 小声的念叨一句,女人心,海底针。 今天就直接跳过了关于QML在qrc文件中的介绍,...

Ev4n
40分钟前
1
0
深入解析js的作用域、预解析机制

虽然,ES6在我们工作中应用得越来越广泛,但是还是很多项目保留着ES5的写法,所以,今天,带着大家重新巩固下ES5下的作用域及预解析机制。 概念: 作用域:域,指的是一个空间、范围、区域,...

前端攻城老湿
43分钟前
1
0
Spring Cloud Feign - 声明式 REST Client

1、Feign是什么 声明式REST client,来自NetFlix。 允许你编写无实现代码调用REST services 替换RestTemplate(甚至更简单) Spring Cloud 为使用Feign提供了包装器 2、怎样使用Feign 对比:...

Benz001
48分钟前
3
0
前端、后端和全栈到底不该学什么

1、前言 在职业规划咨询过程中经常会被问到这样的问题: 老师,我是该深入钻研专精一门,走技术大牛路线,还是所有都要精通,做一个全栈工程师? 类似问题的变种还有,老师我是不是该30岁最迟...

前端攻城小牛
51分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部