Android Java+TCP客戶端+粘包处理

前言
19年那会我还是用java去写的tcp,当然是另一个项目,也没有做沾包处理,今天重新把原来的项目给加上了粘包处理,当然还是选择用java语言.直接在原先的类上直接修改的.

这是我用kotlin写的一个TCP粘包处理,思路也是同样的,只是写法不同,有兴趣可以去看下.
Android Kotlin语言实现+TCP客户端开发+粘包处理

class TcpService implements Runnable

创建一个TCP连接类,该类实现Runnable,连接的过程中需要在子线程中去处理,所以我直接让该类实现接口.

 public TcpService(OnListenerIndustrialObject objectCallBack) {
        this.objectCallBack = objectCallBack;
        isconnect = true;
        // 放两个线程 一个发送的线程 一个读取接收消息的线程
        executorService = Executors.newFixedThreadPool(2);
    }

创建一个构造方法,初始化一些该类所需要的参数.我这方法里使用接口回调,回调一些当前的状态信息等,变量isconnect改变当前的是否可以连接TCP的状态 这里创建了一个只有两个线程的线程池.一个是发送消息的.一个是处理粘包的.

  /**
     * 设置ip地址
     */
    public TcpService setIp(String ip) {
        this.ip = ip;
        return this;
    }

    public TcpService setProt(int prot) {
        this.prot = prot;
        return this;
    }

开始之前需要传输对应的IP端口号,返回本类该对象.也可以链式调用.

 @Override
    public void run() {
        readQueueData();
        Log.i(TAG, "tcp run");
        while (isconnect) {
            try {
                socket = new Socket();
                SocketAddress socketAddress = new InetSocketAddress(ip, prot);
                Log.i(TAG, "" + ip + "," + prot);
                socket.connect(socketAddress, 30000);
                if (socket.isConnected()) {
                    createIo();
                }
                //连接成功发送注册命令
                sendCommand("00030000020000000a01000000000000000000");
                //tcp收到命令之后返回来的数据
                readTCPData();
            } catch (Exception e) {
                e.printStackTrace();
                Log.i(TAG, "tcp connect exception");
                Log.i(TAG, "TCP reconnects in 5 seconds");
                objectCallBack.isNetWorkNormally(false);
                closeTcp();
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
        }
        Log.i(TAG, "tcp end");
    }

同样把这些写在循环当中,以防止因某些情况断开连接导致无法重新连接
readQueueData()这个方法就是处理粘包的问题,这个放在最后讲

private void createIo() throws Exception {
        in = new DataInputStream(socket.getInputStream());
        out = new DataOutputStream(socket.getOutputStream());
    }

连接成功之后创建SocketIO流,接收发送消息用

 /**
     * 发送命令
     *
     * @param registerCommand 命令
     */
    synchronized void sendCommand(final String registerCommand) {
        executorService.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (out != null) {
                        out.write(CodeUtil.hex2byte(registerCommand));
                        out.flush();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

开启发送线程,这个命令权限公开出去,在外部可以直接调用

  private void readTCPData() {
        try {
            byte[] bytes = new byte[1024];
            int len = 0;
            if (in != null) {
                objectCallBack.isNetWorkNormally(true);
                while ((len = in.read(bytes)) != -1) {
                    for (int i = 0; i < len; i++) {
                        queueData.offer(bytes[i]);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

开启接收命令的方法.
可以看到在接收命令中我们把采集的数据都放在一个队列当中.我们之前开重写的run方法中第一个的方法readQueueData()就是处理这个字节队列的

  private ConcurrentLinkedQueue<Byte> queueData = new ConcurrentLinkedQueue<>();

我们看一下处理粘包的过程,我直接把思路写在注释里.这样配合代码看起来也不用上下翻.
先看下命令的消息体格式是什么样子

//消息头 他为什么变绿了? 8个字节
18 02 00 00 03 00 00 00
//消息长度 一个字节,此长度包含消息长度本身和消息体和消息结尾,不包含消息头.
2C  //转换出来就是44 
//消息体  41个字节
FF00FFFE0100000000000000000000000000000000000000000000000000000000000000000000000000
//消息尾 消息尾一般都是两个字节0D0A
0D0A
// 这个和kotlin那篇文章的消息体格式是不相同的 不要弄混了
 private void readQueueData() {
        executorService.execute(() -> {
            int outCount = 0;
            // 创建只有一个消息头的字节数组+消息长度的字节 也就是9个字节
            byte[] b = new byte[9];
            while (true) {
            	// 持续遍历到第9个字节我们就需要拿它的消息体
                if (outCount == 9) {
                	// 分析当前的消息体的长度
                    int length = CodeUtil.byteToInt(b[8]);
                    int inCount = 0;
                    // 当前的数据长度加上之前的消息头,也就是 44+9
                    byte[] bytes = new byte[length + 9];
                    // 长度到达9的时候创建一个新的字节数组,把之后的数据添加到新的字节数组中
                    while (true) {
                        Byte inPoll = queueData.poll();
                        if (inPoll == null) {
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            continue;
                        }
                        bytes[inCount + 9] = inPoll;
                        if (inCount == length - 1) {
                        	// 消息体拿的数量 和消息体的长度的一致则解析数据,并且跳出内循环
                        	// 继续解析下次的消息体
                            outCount = 0;
                            // 把当前的消息头的数组的添加进循环到9的时候创建的数组中
                            System.arraycopy(b, 0, bytes, 0, 9);
                            // 解析数据
                            analysisData(bytes);
                            break;
                        }
                        inCount++;
                    }
                }
                Byte poll = queueData.poll();
                if (poll == null) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                // 根据当前的遍历的下标去赋值
                b[outCount] = poll;
                outCount++;
            }
        });
    }

这就是所有的东西了,

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值