前提
之前写过两篇关于socket的文章,但是,只是简单的介绍了一下关于socket Tcp和Udp的简单使用。如果没有看过的朋友可以去看看Android Socket编程(tcp)初探和Android Socket编程(udp)初探。相信很多朋友在公司使用socket开发的时候都会自定义协议来传递信息。一方面是为了安全排除脏数据,另一个方面是为了更加高效的处理自己所需要的数据。今天就来介绍一下关于socket自定义协议和使用Protocol Buffer解析数据。
首先
既然说到了Protocol Buffer,那么我们就简单介绍一下Protocol Buffer是什么?并且使用为什么要使用Protocol Buffer?
- 1、什么是Protocol Buffer
一种 结构化数据 的数据存储格式(类似于 XML、Json ),其作用是通过将 结构化的数据 进行 串行化(序列化),从而实现 数据存储 / RPC 数据交换的功能。至于更详细的用法和介绍请移步Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?
-
2、为什么要使用Protocol Buffer
在回答这个问题之前,我们还是先给出一个在实际开发中经常会遇到的系统场景。比如:我们的客户端程序是使用Java开发的,可能运行自不同的平台,如:Linux、Windows或者是Android,而我们的服务器程序通常是基于Linux平台并使用C++或者Python开发完成的。在这两种程序之间进行数据通讯时存在多种方式用于设计消息格式,如:
1、 直接传递C/C++/Python语言中一字节对齐的结构体数据,只要结构体的声明为定长格式,那么该方式对于C/C++/Python程序而言就非常方便了,仅需将接收到的数据按照结构体类型强行转换即可。事实上对于变长结构体也不会非常麻烦。在发送数据时,也只需定义一个结构体变量并设置各个成员变量的值之后,再以char*的方式将该二进制数据发送到远端。反之,该方式对于Java开发者而言就会非常繁琐,首先需要将接收到的数据存于ByteBuffer之中,再根据约定的字节序逐个读取每个字段,并将读取后的值再赋值给另外一个值对象中的域变量,以便于程序中其他代码逻辑的编写。对于该类型程序而言,联调的基准是必须客户端和服务器双方均完成了消息报文构建程序的编写后才能展开,而该设计方式将会直接导致Java程序开发的进度过慢。即便是Debug阶段,也会经常遇到Java程序中出现各种域字段拼接的小错误。
2、 使用SOAP协议(WebService)作为消息报文的格式载体,由该方式生成的报文是基于文本格式的,同时还存在大量的XML描述信息,因此将会大大增加网络IO的负担。又由于XML解析的复杂性,这也会大幅降低报文解析的性能。总之,使用该设计方式将会使系统的整体运行性能明显下降。
对于以上两种方式所产生的问题,Protocol Buffer均可以很好的解决,不仅如此,Protocol Buffer还有一个非常重要的优点就是可以保证同一消息报文新旧版本之间的兼容性。对于Protocol Buffer具体的用法请移步Protocol Buffer技术详解(语言规范)今天主要讲解的是socket自定义协议这块
其次
说了那么多,我们来看看我们今天的主要内容— 自定义socket协议
先看一张心跳返回的图
-
1、Protobuf协议
-
假设客户端请求包体数据协议如下
request.proto
syntax = "proto3";
// 登录的包体数据
message Request {
int32 uid = 0;
string api_token = 1;
}
发送的格式:
{包头}{命令}{包体}
{包头} -> 包体转成protubuf的长度
{命令} -> 对应功能的命令字参数
{包体} -> 对应的protubuf数据
- 假设服务端返回包体数据协议
response.proto
syntax = "proto3";
// 登录成功后服务器返回的包体数据
message Response {
int32 login = 1;
}
服务器返回的格式:
{包头}{命令}{状态码}{包体}
{包头} -> 包体转成protubuf的长度
{命令} -> 对应功能的命令字参数
{状态码} -> 对应状态的状态码
{包体} -> 对应的protubuf数据
-
2、客户端socket写法
-
分析:试想一下,要socket不会因为手机屏幕的熄灭或者其他什么的而断开,我们应该把socket放到哪里去写,又要怎么保证socket的连接状态呢?对于Android来说放到 service里面去是最合适的,并且为了保证连接状态。那么,就要发送一个心跳包保证连接状态。既然这样,那么我们来写service和socket。
-
3、service写法
public class SocketService extends Service { Thread mSocketThread; Socket mSocket; InetSocketAddress mSocketAddress; //心跳线程 Thread mHeartThread; //接收线程 Thread mReceiveThread; //登录线程 Thread mLoginThread; boolean isHeart = false; boolean isReceive = false; SocketBinder mBinder = new SocketBinder(this); public SocketService() { } @Override public void onCreate() { super.onCreate(); createConnection(); receiveMsg(); isHeart = true; isReceive = true; } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public int onStartCommand(Intent intent, int flags, int startId) { startGps(); sendHeart(); if (!TextUtils.isEmpty(intent.getStringExtra(AppConfig.SERVICE_TAG))) { String TAG = intent.getStringExtra(AppConfig.SERVICE_TAG); switch (TAG) { case AppConfig.STOP_SERVICE_VALUE: {//停止服务 ClientSocket.getsInstance().shutDownConnection(mSocket); stopSelf(); mSocket = null; mHeartThread = null; mReceiveThread = null; mLoginThread = null; mSocketThread = null; isHeart = false; isReceive = false; break; } default: break; } } return super.onStartCommand(intent, flags, startId); } /** * 发送心跳包 */ private void sendHeart() { mHeartThread = new Thread(new Runnable() { @Override public void run() { while (isHeart) { ClientSocket.getsInstance().sendHeart(mSocket, SocketStatus.HEART_CODE); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTra