Androidsocket⾼级⽤法(⾃定义协议和ProtocolBuffer使⽤)前提
之前写过两篇关于socket的⽂章,但是,只是简单的介绍了⼀下关于socket Tcp和Udp的简单使⽤。
如果没有看过的朋友可以去看看Android Socket编程(tcp)初探和Android Socket编程(udp)初
探。相信很多朋友在公司使⽤socket开发的时候都会⾃定义协议来传递信息。⼀⽅⾯是为了安全排除
脏数据,另⼀个⽅⾯是为了更加⾼效的处理⾃⼰所需要的数据。今天就来介绍⼀下关于socket⾃定义
协议和使⽤Protocol Buffer解析数据。
⾸先
安卓intent用法既然说到了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数据
客户端socket写法
2、客户端
分析:试想⼀下,要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() {
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.StringExtra(AppConfig.SERVICE_TAG))) {    String TAG = StringExtra(AppConfig.SERVICE_TAG);
switch (TAG) {
case AppConfig.STOP_SERVICE_VALUE: {//停⽌服务
stopSelf();
mSocket = null;
mHeartThread = null;
mReceiveThread = null;
mLoginThread = null;
mSocketThread = null;
isHeart = false;
isReceive = false;
break;
}
default:
break;
}
StartCommand(intent, flags, startId);
}
/**
* 发送⼼跳包
*/
private void sendHeart() {
mHeartThread = new Thread(new Runnable() {
@Override
public void run() {
while (isHeart) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
mHeartThread.start();
}
/**
* 登录
*/
private void login(final double mLatitude, final double mLongitude) {
mLoginThread = new Thread(new Runnable() {
@Override
public void run() {
if (Int(SocketService.this, Constants.USER_ID) != 0 &&
!
TextUtils.String(SocketService.this,
Constants.USER_TOKEN))) {
Request.Request requestLogin =
wBuilder()
.Int(SocketService.this,
Constants.USER_ID))
.String(SocketService.this,
Constants.USER_TOKEN).trim())
.build();
}
}
});
mLoginThread.start();
}
/**
* 创建连接
*
* @return
public void createConnection() {
mSocketThread = new Thread(new Runnable() {
@Override
public void run() {
try {
mSocket = new Socket();
mSocketAddress = new InetSocketAddress(AppConfig.TCP_IP, AppConfig.TCP_PORT);
// 设置 socket 读取数据流的超时时间
mSocket.setSoTimeout(20 * 1000);
// 发送数据包,默认为 false,即客户端发送数据采⽤ Nagle 算法;
// 但是对于实时交互性⾼的程序,建议其改为 true,即关闭 Nagle
// 算法,客户端每发送⼀次数据,⽆论数据包⼤⼩都会将这些数据发送出去
mSocket.setTcpNoDelay(true);
/
/ 设置客户端 socket 关闭时,close() ⽅法起作⽤时延迟 30 秒关闭,如果 30 秒内尽量将未发送的数据包发送出去            // socket.setSoLinger(true, 30);
// 设置输出流的发送缓冲区⼤⼩,默认是4KB,即4096字节
mSocket.setSendBufferSize(10 * 1024);
// 设置输⼊流的接收缓冲区⼤⼩,默认是4KB,即4096字节
mSocket.setReceiveBufferSize(10 * 1024);
// 作⽤:每隔⼀段时间检查服务器是否处于活动状态,如果服务器端长时间没响应,⾃动关闭客户端socket
// 防⽌服务器端⽆效时,客户端长时间处于连接状态
mSocket.setKeepAlive(true);
} catch (UnknownHostException e) {
Logger.Message()  "======== UnknownHostException");
e.printStackTrace();
} catch (IOException e) {
createConnection();
Logger.Message()  "========IOException");
e.printStackTrace();
} catch (NetworkOnMainThreadException e) {
Logger.Message()  "========NetworkOnMainThreadException");
e.printStackTrace();
}
}
});
mSocketThread.start();
}
/**
* 接收
*/
private void receiveMsg() {
mReceiveThread = new Thread(new Runnable() {
@Override
public void run() {
while (isReceive) {
try {
if (mSocket != null && mSocket.isConnected()) {
DataInputStream dis = sInstance().getMessageStream(mSocket);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
if (dis != null) {
int length = 0;
int head = 0;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。