android 客户端tcp工具类

在tcp通讯中,如果一次要接收大量数据,可能出现接收不完整的情况,数据被分割了,有的开发人员会通过分批读取,例如每次读取1k数据,然后进行拼接。这种方法并不保险,不好判断是否真正读取完整的次数,因为实际中可能出现当次未读满1k数据而实际上报文仍未接收完成的情况,这时根据未读满1k来判断接收完成就会出问题了。

更好的做法是服务端将数据总长度也加入报文,比如固定4个字节长度存放数据总长度,每次发送前,先将4个字节的长度数据发出,再发送正式数据。客户端同样先接收4个字节,并转换成int得到数据总长度,然后一次完整读取。

以下提供一个客户端tcp工具类:

        服务端发送数据前会先发送4个字节总长度数据,按小端模式存放。

public class TcpClient {
    private static TcpClient instance;
    private final String host = "127.0.0.1";
    private final int port = 31001;
    private Socket socket;
    private DataOutputStream out;
    private DataInputStream dataInputStream;
    private PrintWriter pw;
    private String revMsg = "";
    private static final String TAG = "TCP";
    private static long oldPongTime = System.currentTimeMillis();
    private byte[] dataBytes;
    private byte[] lenbuff = new byte[4];
    private HeartThread heartThread = null;
    private TcpClient() {
    }

    public static TcpClient getInstance() {
        if (instance == null) {
            synchronized (TcpClient.class) {
                if (instance == null) {
                    instance = new TcpClient();
                }
            }
        }
        return instance;
    }

    public void connect(){
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    socket = new Socket(host, port);
                    out = new DataOutputStream(socket.getOutputStream());
                    pw = new PrintWriter(out,true);
                    dataInputStream = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
                    sendMessage(JsonUtils.getInstance().formtGetJsonCommon("login"));
                    SystemClock.sleep(1000);
                    sendMessage(JsonUtils.getInstance().formtGetJsonCommon("ping"));
                    sendHeart();
                    int totalLen = 0;
                    ByteBuffer buffer;
                    while (true){
                        if(dataInputStream == null){
                            Thread.interrupted();
                            LogUtil.showLog("tcp","==soket in is null");
                            break;
                        }
                        dataInputStream.read(lenbuff);
                        buffer = ByteBuffer.wrap(lenbuff);
                        buffer.order(ByteOrder.LITTLE_ENDIAN);//小端
                        totalLen = buffer.getInt();
                        LogUtil.showLog("tcp","==totalLen=="+Arrays.toString(lenbuff)+"==len=="+totalLen);
                        if(totalLen == 0){
                            reconnect();
                            break;
                        }
                        dataBytes = new byte[totalLen];
                        dataInputStream.readFully(dataBytes);
                        revMsg = new String(dataBytes);
                        Arrays.fill(dataBytes, (byte) 0); //清空缓存区
                        if (revMsg.contains("pong")) {
                            oldPongTime = System.currentTimeMillis();
                        } else {
                            JsonUtils.getInstance().parseResultJson(revMsg);
                        }
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    public synchronized void sendMessage(String message) {
        LogUtil.showLog(TAG,"==send msg=="+message);
        new Thread(){
            @Override
            public void run() {
                super.run();
                try {
                    pw.println(message);
                  //  out.writeBytes(message + "\n");
                 //   out.flush();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

    public void sendHeart(){
        if(heartThread != null && !heartThread.isInterrupted()){
            heartThread.interrupt();
            heartThread = null;
        }
            heartThread = new HeartThread();
        heartThread.start();
    }

    class HeartThread extends Thread{
        @Override
        public void run() {
            super.run();
            while (true){
                try {
                    Thread.sleep(20*1000);
                    if(pw == null) {
                        LogUtil.showLog("tcp","==send heart pw is null,so break");
                        break;
                    }
                    pw.println(JsonUtils.getInstance().formtGetJsonCommon("ping"));
                    if(System.currentTimeMillis() - oldPongTime > 40*1000){
                        //TCP 重连
                        TcpClient.getInstance().reconnect();
                       // Thread.interrupted();
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void reconnect(){
        LogUtil.showLog("tcp","===reconnect==");
        if(dataInputStream != null){
            try {
                dataInputStream.close();
                dataInputStream = null;
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(out != null){
            try {
                out.close();
                out = null;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if(pw != null){
            pw.close();
            pw = null;
        }
        connect();

    }

该工具类采用单例模式实现,包含消息长度大小端计算、定时发送心跳包、断线重连、消息发送、消息接收

关于大小端计算方式:android 有标准方法将字节数组转成int:

ByteBuffer buffer = ByteBuffer.wrap(lenbuff);
buffer.order(ByteOrder.LITTLE_ENDIAN);//小端
int totalLen = buffer.getInt();

计算原理如下:

要将一个byte数组(例如 `[91, 1, 0, 0]`)按照小端模式(Little-endian)转换为十进制整数,需要对每个字节进行处理,然后将它们组合成一个整数。具体步骤如下:

1. 将每个字节转换为无符号整数。在此例中,byte数组已经表示为无符号整数,所以无需进行转换。
2. 将每个字节乘以对应的权重(1个字节=8bit=2的8次幂;即256的幂次方)。权重从0开始,逐渐增加。小端模式表示低位字节在前,高位字节在后。
3. 将所有结果求和,得到最终的十进制整数。

对于给定的byte数组 `[91, 1, 0, 0]`,按照小端模式,可以得到以下计算过程:

```
(91 * 256^0) + (1 * 256^1) + (0 * 256^2) + (0 * 256^3)
```

计算得到:

```
(91 * 1) + (1 * 256) + (0 * 65536) + (0 * 16777216)
```

进一步计算:

```
91 + 256 + 0 + 0
```

最终结果为:

```
347
```

所以,按照小端模式,byte数组 `[91, 1, 0, 0]` 转换为十进制整数为 `347`。

猜你喜欢

转载自blog.csdn.net/HuanWen_Cheng/article/details/131258002