美文网首页
spring-boot 建socket服务器,处理物联网设备的粘

spring-boot 建socket服务器,处理物联网设备的粘

作者: 青岛大桥_Android到后端 | 来源:发表于2021-04-09 17:07 被阅读0次

首先dis一下某些物联网设备的通信协议。

您用TCP Socket协议也就罢了,可是您还通信协议只有开始符,没有结束符是怎么回事。
您还用不可见字符,二进制的,不便于调试。请问这都什么年代了,您的MCU(单片机)性能有那么弱吗?
来看一下通信协议:

通信帧格式:
    名称  长度  说明
        帧头

     开始标识           2Byte   0xEF3A (高字节在前)
    数据长度                   2 Byte   长度是指信道、协议版本、命令、内容、CRC 五者的字节之和。(高字节在前)
    信道                           1 Byte                            0x01 服务发出    4G/2G
                                                                     0x02 设备发出  
    协议版本                   1 Byte   当前版本 0x01

帧数据 
        命令                             1 Byte   
    数据                            N Byte    
     
          校验    CRC16   2 Byte  将开始标识、长度、信道、协议版本、命令和内容这六部分
内容的所有字节进行 CRC 计算(高字节在前)

别欺负我不懂MCU, 我以前也是干硬件出身,PCB也画过,MCU软件也写过,机器也量产过。
以上叨B叨那么多,硬件改不了,还是要对接啊。以下开始:

  1. 来人,上netty
image.png

其它的也就不説了,各种文章太多了,
来看看核心部分:

public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {

        //这是用最简单的分割符来分割
        //ByteBuf delimiter = Unpooled.copiedBuffer("#".getBytes());

        //添加编解码
        //socketChannel.pipeline().addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
        //socketChannel.pipeline().addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));

        //前置,处理了普通分隔符作为报文结束符的分包和粘包问题,不过不适合此例子
        //socketChannel.pipeline().addLast(new MyDelimiterBasedFrameDecoder(1024, delimiter));

        socketChannel.pipeline().addLast("decoder", new MyBinDecoder(CharsetUtil.UTF_8));
        socketChannel.pipeline().addLast("encoder", new MyBinEncoder(CharsetUtil.UTF_8));
        socketChannel.pipeline().addLast(new NettyServerHandler());
    }
}
  1. 怎么写具体的协议解析

然后,就是我们定义的MyBinDecoder 最重要:

public class MyBinDecoder extends MessageToMessageDecoder<ByteBuf> {
    private final Charset charset;

    //上次的半包. 用于处理分包情况
    private String lastHalfPacket = "";

    public MyBinDecoder() {
        this(Charset.defaultCharset());
    }

    public MyBinDecoder(Charset charset) {
        if (charset == null) {
            throw new NullPointerException("charset");
        } else {
            this.charset = charset;
        }
    }

    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        List<String> cmdList = parseCmdList(msg);

        if(cmdList!=null && !cmdList.isEmpty()) {
            log.info("添加list");
            out.addAll(cmdList);
        }else{
            System.out.println("暂未发现指令");
        }

    }

    byte[] starter = new byte[]{ (byte)0xEF, (byte)0x3A};
    final int CMD_HEADER_LENGTH = 6;

    private List<String> parseCmdList(ByteBuf originBuf){
        ByteBuf buf = Unpooled.buffer(1024);
        buf.writeBytes(ByteBufUtil.decodeHexDump(lastHalfPacket));
        buf.writeBytes(originBuf);
        List<String> result = new ArrayList<>();

        log.info("开始遍历 buf readableBytes=" + buf.readableBytes());

        try {
            while(buf.readableBytes() >= CMD_HEADER_LENGTH) {
                byte[] bStart = new byte[2];
                //String hex = ByteBufUtil.hexDump(b);
                buf.getBytes(buf.readerIndex(), bStart);
                log.info("读starter:" + ByteBufUtil.hexDump(bStart));
                if (compareBytes(bStart, starter)) {
                    log.info("找到了start");
                    //判断这次长度够不够读,如果不够读直接退出
                    //如果够读,读进来,插进cmd list
                        byte[] bHeader = new byte[CMD_HEADER_LENGTH];
                        //buf.markReaderIndex();
                        //buf.readBytes(bHeader);
                        buf.getBytes(buf.readerIndex(), bHeader);
                        int dataLength = bHeader[2] * 256 + bHeader[3];
                        //buf.resetReaderIndex();
                        int THEORYLENGTH = 4 + dataLength;
                        log.info("命令总长度: " + THEORYLENGTH);

                        if(buf.readableBytes() >= THEORYLENGTH) {
                            log.info("正在读全长");
                            byte[] bCmdFrame = new byte[THEORYLENGTH];
                            buf.readBytes(bCmdFrame);
                            //readerIndex += THEORYLENGTH;
                            String hexCmd = ByteBufUtil.hexDump(bCmdFrame);
                            log.info("找到一个指令啦:" + hexCmd);
                            result.add(hexCmd);
                            log.info("读缓冲区还有:" + buf.readableBytes());
                            continue;
                        }else{
                            log.info("命令总长未达预期,下次再读");
                            int halfPacketNum = buf.readableBytes();
                            byte[] bHalfPacket = new byte[halfPacketNum];
                            buf.readBytes(bHalfPacket);
                            if(halfPacketNum>=1) {
                                String halfPacketStr = ByteBufUtil.hexDump(bHalfPacket);
                                log.info("剩下半包"+halfPacketStr);
                                lastHalfPacket = halfPacketStr==null? "": halfPacketStr;
                            }
                            return result;
                        }

                } else {
                    //读指针向后移动一个
                    log.info("没找到starter,读指针向后移动一个");
                    buf.readBytes(1);
                    continue;
                }
            }

            int halfPacketNum = buf.readableBytes();
            if(halfPacketNum > 0) {
                log.info("HEADER命令头未达预期,下次再读");
                byte[] bHalfPacket = new byte[halfPacketNum];
                buf.readBytes(bHalfPacket);
                String halfPacketStr = ByteBufUtil.hexDump(bHalfPacket);
                log.info("剩下半包"+halfPacketStr);
                lastHalfPacket = halfPacketStr==null? "": halfPacketStr;
            }else{
                log.info("余下字节恰好为0,本轮读完了");
            }
        }catch (Exception e){
            e.printStackTrace();
            log.warn(e.toString());
            return result;
        }

        return result;
    }

    private boolean compareBytes(byte[] b1, byte[] b2){
        if(b1==null && b2==null){
            return true;
        }
        if( (b1==null && b2!=null) || (b1!=null && b2==null)){
            return false;
        }
        if(b1.length != b2.length){
            return false;
        }
        for(int i = 0; i<b1.length; i++){
            if( b1[i]!=b2[i]){
                return false;
            }
        }
        return true;
    }

}
  1. 后记
    这是个半成品,只是打印出来了十六进制转成了HEX STRING的结果。后续对指令的处理,自己再加吧。

伸手党可去CSDN下载源码
https://download.csdn.net/download/stephenzhu/16593276

相关文章

  • spring-boot 建socket服务器,处理物联网设备的粘

    首先dis一下某些物联网设备的通信协议。 您用TCP Socket协议也就罢了,可是您还通信协议只有开始符,没有结...

  • 即时通讯

    iOS即时通讯,从入门到“放弃”?socket的半包,粘包与分包的问题iOS 处理socket粘包问题iOS___...

  • Socket粘包处理

  • Socket粘包处理

    什么是粘包 TCP有粘包现象,而UDP不会出现粘包。 TCP(Transport Control Protocol...

  • 使用TCP收发消息需要处理的常见问题

    要实现服务器和客户端之间完整的收发消息,需要处理以下的几个问题。 粘包半包问题 粘包就是一次从socket缓冲区中...

  • 快速了解IOT产品架构

    设备连接物联网平台,与物联网平台进行数据通信。物联网平台可将设备数据流转到其他阿里云产品中进行存储和处理。这是构建...

  • 三分钟实现手机控制Arduino/esp8266/esp32

    blinker是什么? blinker是一个物联网接入方案,旨在让大家可以轻松畅快地DIY物联网设备。 其由服务器...

  • TCP socket通信

    TCP Socket 通信 在物联网开发中,通过串口服务器将串口数据转化成网络信号,服务器与终端之间的链接就相应的...

  • 设备离线检测方案设计

    物联网设备通过某种通讯方式,与服务器进行链接。 我们通过心跳包来确定设备是否在线。 有些设备用的是 TCP/IP...

  • iOS下的UDP广播

    身处物联网崛浪潮中,不懂socket似乎有点说不过去。WiFi设备建立连接的时候,基本都是用UDP广播。本文就来聊...

网友评论

      本文标题:spring-boot 建socket服务器,处理物联网设备的粘

      本文链接:https://www.haomeiwen.com/subject/arqwkltx.html