Android中的socket长连接问题(包括心跳机制、多线程数据发送)
来源:互联网 发布:ps cc mac 安装失败 编辑:程序博客网 时间:2024/05/24 05:59
前阶段的一个项目,需要实现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.利用同步的方法读取服务器返回的数据,因为有”心跳机制“的存在,在数据接收的时候,就会出现可能数据同一时刻返回的情况,所以需要用一个锁来进行锁定,读取一条返回的指令后,再读取下一条指令。
参考自以下几篇文章:
http://blog.csdn.net/androidforwell/article/details/55102185
http://blog.csdn.net/ttkatrina/article/details/72956338
http://blog.csdn.net/jj583500596/article/details/76586696