前阶段的一个项目,需要实现socket的长连接,即需要实现心跳连接,由于之前只做过简单的socket通讯,所以没有太多的相关知识,只能在度娘上边儿潜水,从0开始学习心跳机制,其实,只要稍微了解网络通讯的业界大佬对此应该都是不屑的。“心跳”说白了就是为了保证长连接,在正常的socket通讯中,只要服务端socket和客户端socket连接成功后,就可以进行数据的传递了,但是有些时候,服务器端不知道客户端什么时候发来数据,则需要进行阻塞式的接收数据,这就形成了长连接,但是如何保证二者一直处于连接状态呢?这就需要一个类似于监控设备的东西来定时的报告一次,是否连接,这就形成了“心跳”,二者之间的通过发送反馈一个既定的字节或者其他数据,来监听连接是否正常,从而告诉客户端此时的连接状态。
以下是我的项目中的一段片段代码:
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.example.rkgg.UI.DBManager;
import com.example.rkgg.UI.TypeConversion;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* Created by RKGG on 2017/9/21. */
public class BackService extends Service {
private static final String TAG = "BackService";
/**
* 心跳检测时间 */
private static final long HEART_BEAT_RATE = 25 * 1000;
/**
* 常规数据心跳时间 */
private static final long REGULAR_BEAT_RATE = 30 * 1000;
/**
* 主机IP */
private static String HOST = "";
/**
* 端口号 */
public static final int PORT = 8865;
/**
* 登录广播 */
public static final String LOGIN_ACTION = "com.example.login_ACTION";
/**
* 消息广播 */
public static final String MESSAGE_ACTION = "com.example.message_ACTION";
/**
* 心跳广播 */
public static final String HEART_BEAT_ACTION = "com.example.heart_beat_ACTION";
private boolean HEARTBEAT_NORMAL = false;
/**
* 登出广播 */
public static final String LOGOUT_ACTION = "com.example.logout_ACTION";
/**
* 释放资源广播 */
public static final String RELEASESOCKET_ACTION = "com.example.releaseSocket_ACTION";
private long sendTime = 0L;
private String startHours;
private String endHours;
private int workCounts;
private int workTime;
public DBManager dbManager;
/**
* 弱引用 在引用对象的同时允许对垃圾对象进行回收 */
public WeakReference<Socket> mSocket;
private Object lock = new Object();
private ReadThread mReadThread;
private TypeConversion tc;
private TcpCommond tcd;
public static boolean flag = false;
public static int socket_flag = 0;
public static int logout_flag = 0;
//返回的云空间结构
public static byte[] cloud_results;
//下载文件的内容
public static byte[] file_down;
//分块数据长度
public static byte[] block_down;
private IBackService.Stub iBackService = new IBackService.Stub() {
@Override
public boolean sendMessage(String message) throws RemoteException {
String s = "";
byte[] bs = tc.hexStringToByteArray(message);
return sendMsg(bs);
}
@Override
public boolean sendCommond(byte[] bytes) throws RemoteException {
return sendMsg(bytes);
}
};
@Override
public IBinder onBind(Intent arg0) {
return iBackService;
}
@Override
public void onCreate() {
super.onCreate();
dbManager = new DBManager(this);
tc = new TypeConversion();
tcd = new TcpCommond(this);
new InitSocketThread().start();
}
@Override
public void onDestroy() {
super.onDestroy();
//在销毁service的时候,结束线程,这里不需要再释放socket
if (heartHandler.getLooper().getThread().getState().toString().equals("RUNNABLE")) {
heartHandler.removeCallbacks(heartBeatRunnable);
}
if (regularHandler.getLooper().getThread().getState().toString().equals("RUNNABLE")) {
regularHandler.removeCallbacks(regularRunnable);
}
sendMsg(tcd.deviceLogout());
flag = false;
socket_flag = 0;
}
// 发送心跳包
public Handler heartHandler = new Handler();
public Runnable heartBeatRunnable = new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - sendTime >= HEART_BEAT_RATE) {
new Thread(new Runnable() {
@Override
public void run() {
if (flag == true) {//判断socket是否连连接
boolean isSuccess = sendMsg(tcd.heartBeatbag());//如果发送失败,就重新初始化一个socket
if (!isSuccess) {
heartHandler.removeCallbacks(heartBeatRunnable);
mReadThread.release();
releaseLastSocket(mSocket);
new InitSocketThread().start();
}
}
}
}).start();
}
heartHandler.postDelayed(this, HEART_BEAT_RATE);
}
};
//常规数据
private Handler regularHandler = new Handler();
private Runnable regularRunnable = new Runnable() {
@Override
public void run() {
if (System.currentTimeMillis() - sendTime >= REGULAR_BEAT_RATE) {
new Thread(new Runnable() {
@Override
public void run() {
if (flag == true) {
boolean isSuccess = sendMsg(tcd.regularData());
if (!isSuccess) {
regularHandler.removeCallbacks(regularRunnable);
mReadThread.release();
releaseLastSocket(mSocket);
new InitSocketThread().start();
}
}
}
}).start();
}
regularHandler.postDelayed(this, REGULAR_BEAT_RATE);
}
};
//发送定位数据
private Handler gpsHandler = new Handler();
private Runnable gpsRunnable = new Runnable() {
@Override
public void run() {
if (HEARTBEAT_NORMAL) {
boolean isSuccess = sendMsg(tcd.positioningData());
if (!isSuccess) {
gpsHandler.removeCallbacks(gpsRunnable);
mReadThread.release();
sendMsg(tcd.positioningData());
releaseLastSocket(mSocket);
new InitSocketThread().start();
}
}
gpsHandler.post(this);
}
};
public boolean sendMsg(byte[] bytes) {
if (null == mSocket || null == mSocket.get()) {
return false;
}
Socket soc = mSocket.get();
try {
if (!soc.isClosed() && !soc.isOutputShutdown()) {
OutputStream os = soc.getOutputStream();
try {
os.write(bytes);
os.flush();
}catch (Exception e){
Log.e("MainActivity","Socket is disconnected!");
}
// 每次发送成功数据,就改一下最后成功发送的时间,节省心跳间隔时间
sendTime = System.currentTimeMillis();
} else {
return false;
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
// 初始化socket
private void initSocket() {
Socket socket = null;
try {
// 域名解析
HOST = InetAddress.getByName("xxxx.xx.xx").getHostAddress();
socket = new Socket(HOST, PORT);
mSocket = new WeakReference<Socket>(socket);
}catch (Exception e){}
sendMsg(tcd.deviceLogin());//登陆设备
mReadThread = new ReadThread(socket);
mReadThread.start();
}
// 释放socket
public void releaseLastSocket(WeakReference<Socket> mSocket) {
if (null != mSocket) {
sendMsg(tcd.deviceLogout());
Socket sk = mSocket.get();
try {
if (!sk.isClosed()) {
sk.close();
}
sk = null;
mSocket = null;
}catch (Exception e){
System.out.println("NULL!!!");
}
}
}
class InitSocketThread extends Thread {
@Override
public void run() {
super.run();
try {
initSocket();
}catch (Exception e){
System.out.println("端口占用!!!");
}
}
}
class ReadThread extends Thread {
private WeakReference<Socket> mWeakSocket;
private boolean isStart = true;
public ReadThread(Socket socket) {
mWeakSocket = new WeakReference<Socket>(socket);
}
public void release() {
isStart = false;
sendMsg(tcd.deviceLogout());
releaseLastSocket(mWeakSocket);
}
//同步方法读取返回得数据
@Override
public void run() {
super.run();
Socket socket = mWeakSocket.get();
if (null != socket) {
try {
InputStream is = socket.getInputStream();
byte[] buffer = new byte[65535];//测试过程发现接收的文件会达到5000多字节
int length;
try {
while (!socket.isClosed() && !socket.isInputShutdown()
&& isStart && ((length = is.read(buffer)) != -1)) {
synchronized (this) {
if (length > 0) {
byte[] temp = new byte[length];
System.arraycopy(buffer, 0, temp, 0, length);
//获取返回值的第3位,表明命令类型
String msg_three = tc.bytesToHexString(temp).substring(4, 6);
//获取返回值的第4位,表明数据返回的情况
String msg_four = tc.bytesToHexString(temp).substring(6, 8);
// 收到服务器过来的消息,就通过Broadcast发送出去
if (msg_three.equals("01")) {//登陆
switch (msg_four) {
case "01":
Log.i(TAG, "登录成功");
//先发送一个常规包,确定在线情况
flag = true;
socket_flag = 1;
sendMsg(tcd.regularData());
//初始化成功返回标志后,开始发送心跳包
heartHandler.postDelayed(heartBeatRunnable, HEART_BEAT_RATE);
//发送广播,防止不同的activity重新new一个Serivce
Intent intent = new Intent(LOGIN_ACTION);
intent.putExtra("condition", "Socket_Connected");
sendBroadcast(intent);
break;
case "02":
Log.i(TAG, "登录超时");
mReadThread.release();
releaseLastSocket(mSocket);
// new InitSocketThread().start();
break;
case "03":
Log.i(TAG, "账号密码错误");
mReadThread.release();
releaseLastSocket(mSocket);
// new InitSocketThread().start();
break;
case "04":
Log.i(TAG, "其他位置登录");
//需要在销毁的时候登出设备,否则会出现这个错误
mReadThread.release();
releaseLastSocket(mSocket);
// new InitSocketThread().start();
break;
default:
break;
}
} else if (msg_three.equals("02")) {//实时上报
switch (msg_four) {
case "01":
Log.i(TAG, "实时信息上报成功");
break;
case "02":
Log.i(TAG, "实时信息上报超时");
break;
default:
break;
}
} else if (msg_three.equals("03")) {// 心跳
switch (msg_four) {
case "01":
Log.i(TAG, "心跳正常");
//心跳正常后,开始发送常规数据,经测试发现,常规数据也需要定时发送,
//否则发送一次后,大概1min左右会出现掉线状态
regularHandler.postDelayed(regularRunnable, REGULAR_BEAT_RATE);
break;
case "":
break;
}
} else if (msg_three.equals("05")) {//登出
switch (msg_four) {
case "01":
Log.i(TAG, "登出成功");
logout_flag = 1;
mReadThread.release();
releaseLastSocket(mSocket);
break;
case "02":
Log.i(TAG, "登出超时");
break;
default:
break;
}
} else {
// 其他消息回复
Intent intent = new Intent(MESSAGE_ACTION);
intent.putExtra("message", tc.bytesToHexString(temp));
sendBroadcast(intent);
}
}
}
}
}catch (Exception e){
flag = false;
socket_flag = 0;
//释放socket
mReadThread.release();
releaseLastSocket(mSocket);
// Intent intent = new Intent(MESSAGE_ACTION);
// intent.putExtra("message", "服务器已断开!");
// sendBroadcast(intent);
System.out.println("网络异常!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
上面的代码主要有几点要说一下:
1.利用handler+runnable实现定时器,将心跳包按照规定的时间定时发送, 而在runnable中创建一个新的线程,这样避免了心跳包发送在UI线程中占用资源。所以在销毁service的时候,需要捕获线程并结束线程(这是我的项目需求,所以一般如果需要保持后台依然连接的话,请忽略),
2.利用同步的方法读取服务器返回的数据,因为有”心跳机制“的存在,在数据接收的时候,就会出现可能数据同一时刻返回的情况,所以需要用一个锁来进行锁定,读取一条返回的指令后,再读取下一条指令。