Android Socket通信 發送心跳包 重連

最近項目中,有使用Socket與後端進行通信,然後簡單的瞭解了下Socket使用,大致流程是配置服務端的Ip、端口號,連接,監聽數據和發送數據,數據的讀取和發送都是以流的形式實現的,然後自己將項目中的代碼寫了簡單的管理類,測試下代碼。下面是demo的兩個界面,連接和發送接收界面

 

這裏使用了一個Tcp調試助手,模擬服務端發送和接收數據,通訊模式選擇TcpService ,本地端口隨意定義一個,保持和demo填寫的端口號一致,demo裏面的ip是你主機的ip地址,cmd下ipconfig查看

代碼

一、初始化

public TcpManager initSocket(final String ip, final String port) {
        this.ip=ip;
        this.port=port;
        /* 開啓讀寫線程*/
        threadStatus =true;
        new ReadThread().start();

        if (socket == null && connectThread == null) {
            connectThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    socket = new Socket();
                    try {
                        /*超時時間爲2秒*/
                        socket.connect(new InetSocketAddress(ip, Integer.valueOf(port)), 2000);
                        /*連接成功的話  發送心跳包*/
                        if (socket.isConnected()) {
                            inputStream = socket.getInputStream();
                            dis = new DataInputStream(inputStream);
                            /*因爲Toast是要運行在主線程的  這裏是子線程  所以需要到主線程哪裏去顯示toast*/
                            Log.e(TAG, "服務連接成功");
                            /*發送連接成功的消息*/
                            if(onSocketStatusListener!=null)
                                onSocketStatusListener.onConnectSuccess();
                            /*發送心跳數據*/
                            sendBeatData();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        if (e instanceof SocketTimeoutException) {
                            Log.e(TAG,"連接超時,正在重連");
                            releaseSocket();
                        } else if (e instanceof NoRouteToHostException) {
                            Log.e(TAG,"該地址不存在,請檢查");
                        } else if (e instanceof ConnectException) {
                            Log.e(TAG,"連接異常或被拒絕,請檢查");
                        }
                    }
                }
            });
            /*啓動連接線程*/
            connectThread.start();
        }
        return this;
    }

二、發送數據

public void sendData(final String data) {
        if (socket != null && socket.isConnected()) {
            /*發送指令*/
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        outputStream = socket.getOutputStream();
                        if (outputStream != null) {
                            outputStream.write((data).getBytes("UTF-8"));
                            outputStream.flush();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }).start();

        } else {
            Log.e(TAG,"socket連接錯誤,請重試");
        }
    }

三、讀取數據

private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            //判斷進程是否在運行,更安全的結束進程
            while (threadStatus) {
                if (inputStream != null) {
                    try {
                        rcvLength = dis.read(buff);
                        if (rcvLength > 0) {
                            rcvMsg = new String(buff, 0, rcvLength, "GBK");
                            //接收到數據,切換主線程,顯示數據
                            handler.post(new Runnable() {
                                @Override
                                public void run() {
                                    if(onReceiveDataListener!=null){
                                        onReceiveDataListener.onReceiveData(rcvMsg);
                                    }
                                }
                            });

                        }
                    } catch (Exception e) {
                        Log.e(TAG,"接收總控數據異常");
                    }
                }

            }
        }
    }

四、全部代碼

package com.yufs.testsocket.tcp;

import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Timer;
import java.util.TimerTask;

public class TcpManager {

    private static final String TAG = TcpManager.class.getSimpleName();
    /*socket*/
    private Socket socket;
    /*連接線程*/
    private Thread connectThread;
    /* 發送輸出流*/
    private OutputStream outputStream;
    /* 讀寫輸入流*/
    private InputStream inputStream;
    private DataInputStream dis;
    /* 線程狀態,安全結束線程*/
    private  boolean threadStatus =false;
    /* 讀取保存進數組*/
    byte buff[]  = new byte[1024*1024*2];
    private String ip;
    private String port;
    private Handler handler = new Handler(Looper.getMainLooper());
    /*默認重連*/
    private boolean isReConnect = true;
    /*倒計時Timer發送心跳包*/
    private Timer timer;
    private TimerTask task;

    /* 心跳週期(s)*/
    private int heartCycle = 30;
    /*接收數據長度*/
    private int rcvLength;
    /*接收數據*/
    private String rcvMsg;
    private TcpManager(){}
    private static TcpManager instance;
    public static synchronized TcpManager getInstance(){
        if(instance==null){
            synchronized (TcpManager.class){
                instance=new TcpManager();
            }
        }
        return instance;
    }

    public TcpManager initSocket(final String ip, final String port) {
        this.ip=ip;
        this.port=port;
        /* 開啓讀寫線程*/
        threadStatus =true;
        new ReadThread().start();

        if (socket == null && connectThread == null) {
            connectThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    socket = new Socket();
                    try {
                        /*超時時間爲2秒*/
                        socket.connect(new InetSocketAddress(ip, Integer.valueOf(port)), 2000);
                        /*連接成功的話  發送心跳包*/
                        if (socket.isConnected()) {
                            inputStream = socket.getInputStream();
                            dis = new DataInputStream(inputStream);
                            /*因爲Toast是要運行在主線程的  這裏是子線程  所以需要到主線程哪裏去顯示toast*/
                            Log.e(TAG, "服務連接成功");
                            /*發送連接成功的消息*/
                            if(onSocketStatusListener!=null)
                                onSocketStatusListener.onConnectSuccess();
                            /*發送心跳數據*/
                            sendBeatData();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                        if (e instanceof SocketTimeoutException) {
                            Log.e(TAG,"連接超時,正在重連");
                            releaseSocket();
                        } else if (e instanceof NoRouteToHostException) {
                            Log.e(TAG,"該地址不存在,請檢查");
                        } else if (e instanceof ConnectException) {
                            Log.e(TAG,"連接異常或被拒絕,請檢查");
                        }
                    }
                }
            });
            /*啓動連接線程*/
            connectThread.start();
        }
        return this;
    }

    /*發送數據*/
    public void sendData(final String data) {
        if (socket != null && socket.isConnected()) {
            /*發送指令*/
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        outputStream = socket.getOutputStream();
                        if (outputStream != null) {
                            outputStream.write((data).getBytes("UTF-8"));
                            outputStream.flush();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                }
            }).start();

        } else {
            Log.e(TAG,"socket連接錯誤,請重試");
        }
    }

    /*定時發送數據*/
    private void sendBeatData() {
        if (timer == null) {
            timer = new Timer();
        }

        if (task == null) {
            task = new TimerTask() {
                @Override
                public void run() {
                    try {
                        outputStream = socket.getOutputStream();
                        Log.i(TAG,"發送心跳包");
                        /*這裏的編碼方式根據你的需求去改*/
                        outputStream.write(("test\n").getBytes("UTF-8"));
                        outputStream.flush();
                    } catch (Exception e) {
                        /*發送失敗說明socket斷開了或者出現了其他錯誤*/
                        Log.e(TAG,"連接斷開,正在重連");
                        /*重連*/
                        releaseSocket();
                        e.printStackTrace();
                    }
                }
            };
        }

        timer.schedule(task, 0, 1000*heartCycle);
    }

    /*釋放資源*/
    private  void releaseSocket(){
        if (task != null) {
            task.cancel();
            task = null;
        }
        if (timer != null) {
            timer.purge();
            timer.cancel();
            timer = null;
        }
        if (outputStream != null) {
            try {
                outputStream.close();

            } catch (IOException e) {
                e.printStackTrace();
            }
            outputStream = null;
        }
        if(inputStream!=null){
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            inputStream=null;
        }
        if(dis!=null){
            try {
                dis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            dis=null;
        }
        if (socket != null) {
            try {
                socket.close();

            } catch (IOException e) {
            }
            socket = null;
        }
        if (connectThread != null) {
            connectThread = null;
        }
        /*重新初始化socket*/
        if (isReConnect) {
            initSocket(ip,port);
        }
    }


    private class ReadThread extends Thread {
        @Override
        public void run() {
            super.run();
            //判斷進程是否在運行,更安全的結束進程
            while (threadStatus) {
                if (inputStream != null) {
                    try {
                        rcvLength = dis.read(buff);
                        if (rcvLength > 0) {
                            rcvMsg = new String(buff, 0, rcvLength, "GBK");
                            //接收到數據,切換主線程,顯示數據
                            handler.post(new Runnable() {
                                @Override
                                public void run() {
                                    if(onReceiveDataListener!=null){
                                        onReceiveDataListener.onReceiveData(rcvMsg);
                                    }
                                }
                            });

                        }
                    } catch (Exception e) {
                        Log.e(TAG,"接收總控數據異常");
                    }
                }

            }
        }
    }

    public interface OnSocketStatusListener{
        void onConnectSuccess();
    }

    public OnSocketStatusListener onSocketStatusListener;

    public void setOnSocketStatusListener(OnSocketStatusListener onSocketStatusListener) {
        this.onSocketStatusListener = onSocketStatusListener;
    }

    public interface OnReceiveDataListener{
        void onReceiveData(String str);
    }

    public OnReceiveDataListener onReceiveDataListener;

    public void setOnReceiveDataListener(OnReceiveDataListener onReceiveDataListener) {
        this.onReceiveDataListener = onReceiveDataListener;
    }
}

總體來說分爲三步:連接、發送、線程監聽數據,這裏連接狀態和接收數據使用了監聽事件,可以考慮使用EventBus。整個代碼寫在一個工具類裏面,裏面還有很多需要優化的地方。不如說將這些寫在服務裏面,服務的生命週期與界面綁定。還有數據的讀取這一方面好像有長度限制,超過一定長度,它就分爲兩次或三次,這個問題待解決,不過應對小數據的通信是沒有問題的。

demo下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章