java socket 自定义协议_Android socket高级用法(自定义协议和Protocol Buffer使用)

本文介绍了如何在Android中使用Service来实现一个稳定的Socket连接,包括心跳包发送、数据接收线程、登录流程以及异常重连机制。通过设置心跳线程保持连接状态,使用自定义协议确保数据传输的准确性。此外,还展示了如何处理心跳包和数据接收,以及服务的生命周期管理。
摘要由CSDN通过智能技术生成

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.printStackTrace();

}

}

}

});

mHeartThread.start();

}

/**

* 登录

*/

private void login(final double mLatitude, final double mLongitude) {

mLoginThread = new Thread(new Runnable() {

@Override

public void run() {

if (PreferencesUtils.getInt(SocketService.this, Constants.USER_ID) != 0 &&

!TextUtils.isEmpty(PreferencesUtils.getString(SocketService.this,

Constants.USER_TOKEN))) {

Request.Request requestLogin =

Request.Request.newBuilder()

.setUid(PreferencesUtils.getInt(SocketService.this,

Constants.USER_ID))

.setApiToken(PreferencesUtils.getString(SocketService.this,

Constants.USER_TOKEN).trim())

.build();

ClientSocket.getsInstance().sendLogin(mSocket, requestLogin, SocketStatus.LOGIN_CODE);

}

}

});

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);

mSocket.connect(mSocketAddress, 20 * 1000);

// 设置 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.e(e.getMessage() + "========+UnknownHostException");

e.printStackTrace();

} catch (IOException e) {

createConnection();

Logger.e(e.getMessage() + "========IOException");

e.printStackTrace();

} catch (NetworkOnMainThreadException e) {

Logger.e(e.getMessage() + "========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 = ClientSocket.getsInstance().getMessageStream(mSocket);

ByteArrayOutputStream bos = new ByteArrayOutputStream();

if (dis != null) {

int length = 0;

int head = 0;

int buffer_size = 4;

byte[] headBuffer = new byte[4];

byte[] cmdBuffer = new byte[4];

byte[] stateBuffer = new byte[4];

length = dis.read(headBuffer, 0, buffer_size);

if (length == 4) {

bos.write(headBuffer, 0, length);

System.arraycopy(bos.toByteArray(), 0, headBuffer, 0, buffer_size);

head = ByteUtil.bytesToInt(headBuffer, 0);

length = dis.read(cmdBuffer, 0, buffer_size);

bos.write(cmdBuffer, 0, length);

System.arraycopy(bos.toByteArray(), 4, cmdBuffer, 0, buffer_size);

int cmd = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(ByteUtil.byte2hex(cmdBuffer)));

int heartNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.HEART));

String discover = Integer.toHexString(0x0101);

int discoverNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.DISCOVER));

int giftNumber = ByteUtil.hexStringToAlgorism(ByteUtil.str2HexStr(SocketStatus.GIFT));

if (cmd == heartNumber) {

length = dis.read(stateBuffer, 0, buffer_size);

bos.write(stateBuffer, 0, length);

System.arraycopy(bos.toByteArray(), 8, stateBuffer, 0, buffer_size);

switch (ByteUtil.bytesToInt(stateBuffer, 0)) {

case SocketStatus.LOGIN_SUCCESS: {//登录成功

Logger.e("登录成功");

mLoginValue = "1";

EventUtils.sendEvent(new Event<>(Constants.MSG_LOGIN_SUCCESS));

break;

}

case SocketStatus.HEART_SUCCESS: {//心跳返回

if (ByteUtil.bytesToInt(stateBuffer, 0) == 200

&& Integer.toHexString(ByteUtil.bytesToInt(cmdBuffer, 0))

.equals(discover)) {

byte[] buffer = new byte[head];

length = dis.read(buffer, 0, head);

bos.write(buffer, 0, length);

Response.Response response = Response.

Response.parseFrom(buffer);

Logger.e(responseExplore.getNickname() + responseExplore.getAvatar());

//发送到activity中对数据进行处理

EventUtils.sendEvent(new Event<>(Constants.MSG_START_DISCOVER_RESULT,

responseExplore));

Logger.e(responseExplore + "=======response");

} else {

Logger.e("心跳返回");

}

break;

}

default:

break;

}

}

} else {

//出错重连

ClientSocket.getsInstance().shutDownConnection(mSocket);

createConnection();

}

} else {

createConnection();

}

}

} catch (IOException ex) {

ex.printStackTrace();

}

try {

Thread.sleep(50);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

});

mReceiveThread.start();

}

@Override

public void onDestroy() {

super.onDestroy();

ClientSocket.getsInstance().shutDownConnection(mSocket);

stopSelf();

mHeartThread = null;

mReceiveThread = null;

mLoginThread = null;

mSocketThread = null;

mStopDiscoverThread = null;

isHeart = false;

isReceive = false;

}

/**

* Binder

*/

public class SocketBinder extends Binder {

private SocketService mService;

public OnServiceCallBack mCallBack;

public SocketBinder(SocketService mService) {

this.mService = mService;

}

/**

* 发送方法

*

* @param object

*/

public void sendMethod(Object object) {

mService.sendMsg(object);

mCallBack.onService(object);

}

/**

* 设置回调

*

* @param callBack

*/

public void setOnServiceCallBack(OnServiceCallBack callBack) {

this.mCallBack = callBack;

}

}

}

分析

上面的service中首先创建socket,然后连接,在socket发生错误的时候(比如网络异常)重新进行创建在连接。然后,开一个接收线程一直接收,每次接收都是接收4个字节的int值进行判断是否可以进入到下一步,如果可以则继续向下。读取4个字节的包头然后读取4个字节的命令 再读取4个字节的状态码 最后读取4个字节的包体,包体就包含我们所需要返回的数据。并且,在刚开始的时候就开启了一个接收线程每隔50毫秒接收一次数据,这样不仅可以读取到心跳包还可以读取到我们需要的数据。在最后,server生命周期结束的时候停止所有的线程。

4、发送数据的类

public class ClientSocket {

private DataOutputStream out = null;

private DataInputStream getMessageStream;

private static ClientSocket sInstance;

private ClientSocket() {

}

/**

* 单例

*

* @return

*/

public static ClientSocket getsInstance() {

if (sInstance == null) {

synchronized (ClientSocket.class) {

if (sInstance == null) {

sInstance = new ClientSocket();

}

}

}

return sInstance;

}

/**

* 登录

*

* @return

*/

public void sendLogin(Socket socket, Request.RequestLogin requestLogin, int code) {

byte[] data = requestLogin.toByteArray();

byte[] head = ByteUtil.intToBytes(data.length);

byte[] cmd = ByteUtil.intToBytes(code);

byte[] bytes = addBytes(head, cmd, data);

if (socket != null) {

if (socket.isConnected()) {

try {

OutputStream os = socket.getOutputStream();

os.write(bytes);

os.flush();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

/**

* 心跳

*

* @param code 关键字(命令)

* @return

*/

public boolean sendHeart(Socket socket, int code) {

boolean isSuccess;

byte[] head = ByteUtil.intToBytes(0);

byte[] cmd = ByteUtil.intToBytes(code);

byte[] bytes = addBytes(head, cmd);

if (socket.isConnected()) {

try {

out = new DataOutputStream(socket.getOutputStream());

out.write(bytes);

out.flush();

isSuccess = true;

} catch (IOException e) {

e.printStackTrace();

isSuccess = false;

}

} else {

isSuccess = false;

}

return isSuccess;

}

/**

* 断开连接

*/

public void shutDownConnection(Socket socket) {

try {

if (out != null) {

out.close();

}

if (getMessageStream != null) {

getMessageStream.close();

}

if (socket != null) {

socket.close();

}

} catch (IOException e) {

e.printStackTrace();

}

}

/**

* 获取服务器返回的流

*

* @param socket

* @return

*/

public DataInputStream getMessageStream(Socket socket) {

if (socket == null) {

return null;

}

if (socket.isClosed()) {

return null;

}

if (!socket.isConnected()) {

return null;

}

try {

getMessageStream = new DataInputStream(new BufferedInputStream(

socket.getInputStream()));

} catch (IOException e) {

e.printStackTrace();

if (getMessageStream != null) {

try {

getMessageStream.close();

} catch (IOException e1) {

e1.printStackTrace();

}

}

}

return getMessageStream;

}

}

分析:

这里使用了单例模式,保证了数据的唯一性,不会重复创建,可以看到登录发送了包头、命令和数据长度,而心跳只是包头和命令,因为包体长度为空,所以不用发送,最后转成4个字节的二进制数据进行发送。这样,proto buffer的优点就体现出来了,方便客户端和服务端的解析。

二进制转换工具类

public class ByteUtil {

/**

* 将2个byte数组进行拼接

*/

public static byte[] addBytes(byte[] data1, byte[] data2) {

byte[] data3 = new byte[data1.length + data2.length];

System.arraycopy(data1, 0, data3, 0, data1.length);

System.arraycopy(data2, 0, data3, data1.length, data2.length);

return data3;

}

/**

* 将3个byte数组进行拼接

*/

public static byte[] addBytes(byte[] data1, byte[] data2, byte[] data3) {

byte[] data4 = new byte[data1.length + data2.length + data3.length];

System.arraycopy(data1, 0, data4, 0, data1.length);

System.arraycopy(data2, 0, data4, data1.length, data2.length);

System.arraycopy(data3, 0, data4, data1.length + data2.length, data3.length);

return data4;

}

/**

* int转byte{}

*/

public static byte[] intToBytes(int value, ByteOrder mode) {

byte[] src = new byte[4];

if (mode == ByteOrder.LITTLE_ENDIAN) {

src[3] = (byte) ((value >> 24) & 0xFF);

src[2] = (byte) ((value >> 16) & 0xFF);

src[1] = (byte) ((value >> 8) & 0xFF);

src[0] = (byte) (value & 0xFF);

} else {

src[0] = (byte) ((value >> 24) & 0xFF);

src[1] = (byte) ((value >> 16) & 0xFF);

src[2] = (byte) ((value >> 8) & 0xFF);

src[3] = (byte) (value & 0xFF);

}

return src;

}

/**

* 16进制表示的字符串转换为字节数组

*

* @param s 16进制表示的字符串

* @return byte[] 字节数组

*/

public static byte[] hexStringToByteArray(String s) {

int len = s.length();

byte[] b = new byte[len / 2];

for (int i = 0; i < len; i += 2) {

// 两位一组,表示一个字节,把这样表示的16进制字符串,还原成一个字节

b[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character

.digit(s.charAt(i + 1), 16));

}

return b;

}

/**

* byte数组中取int数值,本方法适用于(低位在前,高位在后)的顺序,和和intToBytes()配套使用

*

* @param src byte数组

* @param offset 从数组的第offset位开始

* @return int数值

*/

public static int bytesToInt(byte[] src, int offset) {

int value;

value = (int) ((src[offset] & 0xFF)

| ((src[offset + 1] & 0xFF) << 8)

| ((src[offset + 2] & 0xFF) << 16)

| ((src[offset + 3] & 0xFF) << 24));

return value;

}

/**

* byte数组中取int数值,本方法适用于(低位在后,高位在前)的顺序。和intToBytes2()配套使用

*/

public static int bytesToInt2(byte[] src, int offset) {

int value;

value = (int) (((src[offset] & 0xFF) << 24)

| ((src[offset + 1] & 0xFF) << 16)

| ((src[offset + 2] & 0xFF) << 8)

| (src[offset + 3] & 0xFF));

return value;

}

/**

* 将int数值转换为占四个字节的byte数组,本方法适用于(低位在前,高位在后)的顺序。 和

bytesToInt()配套使用

*

* @param value 要转换的int值

* @return byte数组

*/

public static byte[] intToBytes(int value) {

byte[] src = new byte[4];

src[3] = (byte) ((value >> 24) & 0xFF);

src[2] = (byte) ((value >> 16) & 0xFF);

src[1] = (byte) ((value >> 8) & 0xFF);

src[0] = (byte) (value & 0xFF);

return src;

}

/**

* 将int数值转换为占四个字节的byte数组,本方法适用于(高位在前,低位在后)的顺序。 和

bytesToInt2()配套使用

*/

public static byte[] intToBytes2(int value) {

byte[] src = new byte[4];

src[0] = (byte) ((value >> 24) & 0xFF);

src[1] = (byte) ((value >> 16) & 0xFF);

src[2] = (byte) ((value >> 8) & 0xFF);

src[3] = (byte) (value & 0xFF);

return src;

}

/**

* 将字节转换为二进制字符串

*

* @param bytes 字节数组

* @return 二进制字符串

*/

public static String byteToBit(byte... bytes) {

StringBuffer sb = new StringBuffer();

int z, len;

String str;

for (int w = 0; w < bytes.length; w++) {

z = bytes[w];

z |= 256;

str = Integer.toBinaryString(z);

len = str.length();

sb.append(str.substring(len - 8, len));

}

return sb.toString();

}

/**

* 字节数组转为普通字符串(ASCII对应的字符)

*

* @param bytearray byte[]

* @return String

*/

public static String byte2String(byte[] bytearray) {

String result = "";

char temp;

int length = bytearray.length;

for (int i = 0; i < length; i++) {

temp = (char) bytearray[i];

result += temp;

}

return result;

}

/**

* 二进制字符串转十进制

*

* @param binary 二进制字符串

* @return 十进制数值

*/

public static int binaryToAlgorism(String binary) {

int max = binary.length();

int result = 0;

for (int i = max; i > 0; i--) {

char c = binary.charAt(i - 1);

int algorism = c - '0';

result += Math.pow(2, max - i) * algorism;

}

return result;

}

/**

* 字节数组转换为十六进制字符串

*

* @param b byte[] 需要转换的字节数组

* @return String 十六进制字符串

*/

public static String byte2hex(byte b[]) {

if (b == null) {

throw new IllegalArgumentException(

"Argument b ( byte array ) is null! ");

}

String hs = "";

String stmp = "";

for (int n = 0; n < b.length; n++) {

stmp = Integer.toHexString(b[n] & 0xff);

if (stmp.length() == 1) {

hs = hs + "0" + stmp;

} else {

hs = hs + stmp;

}

}

return hs.toUpperCase();

}

/**

* 十六进制字符串转换十进制

*

* @param hex 十六进制字符串

* @return 十进制数值

*/

public static int hexStringToAlgorism(String hex) {

hex = hex.toUpperCase();

int max = hex.length();

int result = 0;

for (int i = max; i > 0; i--) {

char c = hex.charAt(i - 1);

int algorism = 0;

if (c >= '0' && c <= '9') {

algorism = c - '0';

} else {

algorism = c - 55;

}

result += Math.pow(16, max - i) * algorism;

}

return result;

}

/**

* 字符串转换成十六进制字符串

*

* @param str 待转换的ASCII字符串

* @return String 每个Byte之间空格分隔,如: [61 6C 6B]

*/

public static String str2HexStr(String str) {

char[] chars = "0123456789ABCDEF".toCharArray();

StringBuilder sb = new StringBuilder("");

byte[] bs = str.getBytes();

int bit;

for (int i = 0; i < bs.length; i++) {

bit = (bs[i] & 0x0f0) >> 4;

sb.append(chars[bit]);

bit = bs[i] & 0x0f;

sb.append(chars[bit]);

sb.append(' ');

}

return sb.toString().trim();

}

/**

* 16进制转换成字符串

*

* @param hexStr

* @return

*/

public static String hexStr2Str(String hexStr) {

String str = "0123456789ABCDEF";

char[] hexs = hexStr.toCharArray();

byte[] bytes = new byte[hexStr.length() / 2];

int n;

for (int i = 0; i < bytes.length; i++) {

n = str.indexOf(hexs[2 * i]) * 16;

n += str.indexOf(hexs[2 * i + 1]);

bytes[i] = (byte) (n & 0xff);

}

return new String(bytes);

}

/**

* 重写了Inpustream 中的skip(long n) 方法,

* 将数据流中起始的n 个字节跳过

*/

public static long skipBytesFromStream(InputStream inputStream, long n) {

long remaining = n;

// SKIP_BUFFER_SIZE is used to determine the size of skipBuffer

int SKIP_BUFFER_SIZE = 2048;

// skipBuffer is initialized in skip(long), if needed.

byte[] skipBuffer = null;

int nr = 0;

if (skipBuffer == null) {

skipBuffer = new byte[SKIP_BUFFER_SIZE];

}

byte[] localSkipBuffer = skipBuffer;

if (n <= 0) {

return 0;

}

while (remaining > 0) {

try {

nr = inputStream.read(localSkipBuffer, 0,

(int) Math.min(SKIP_BUFFER_SIZE, remaining));

} catch (IOException e) {

e.printStackTrace();

}

if (nr < 0) {

break;

}

remaining -= nr;

}

return n - remaining;

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值