12月19, 2019

【工作】记一次TCP数据传输的问题修复过程

背景:前一阵接了某家公司的车辆数据,接入方式是使用底层的tcp传输协议,数据处理中间层框架使用mina,联调完后发现时不时会出现数据丢失现象。

解决过程

  • 首先想到可能是数据包解析方面的问题,于是翻了下对方提供的协议文档,按照对方提供的协议,发现字节没有对错,咨询对方技术后人家说不是按数字节解析,而是每个字段前面对应的 标识解析。

    之后连夜写(其实是拷贝粘贴)了个通用解析方法,部署后发现大多数丢的数据恢复了,但偶尔还是有那么几个数据丢失,作为一个对工作极其负责(较真)的人,我决定彻查到底。

  • 排除了解析问题,接下来就是要确认下是不是存在网络丢包,于是想用ping 测试下丢包率,结果对方服务器设置了限制ping,无奈,接下来只能祭出我的杀手锏了,秀一波神操作。

  • 使用tcpdump命令:

    tcpdump host 对方服务器ip and port 对方服务器端口 -s 0 -Z root -C 50 -W 50 -w tcpdump
    

    这句命令的意思是接收对方发的tcp数据包,以50M大小文件为一个存储单位,最大存储50个 在执行这句命令的同时,盯着线上实时打印的日志,发现有丢数据的情况记录下日志快照,并停止tcpdump 命令,将dump下来的包拷贝到本地。

  • 用wireshark 解析,协议中定的数据起止标识分别是7e7f,

  • 在wireshark中发现问题原因有二:

    其一:有的一段完整数据是分散在两个数据包中(即数据起点标识'7e'在第一个数据包,终止标识‘7f’在第二个数据包),而我的实现逻辑(如下程序)是只解析一个数据包中是否有完整的起止标识。

    其二:程序中仅判断了起止标识,未判断数据长度与数据包中的解析到的包长度是否相等。

    Context ctx = getContext(session);
    List<Integer> buffer = new ArrayList<>();//ctx.getBuffer();
    
    //开始解析数据
    while (in.hasRemaining()) {
    
        int b = in.get() & 0xff;
    
        if (buffer.size() > BUFFER_MAX) {
              LogUtil.LogType.errorLog.error("清空缓存");
              buffer.clear();
         }
    
          buffer.add(b);
    
         //此处仅判断了 开始标志: 0x7e 和 结尾标志 : 0x7f
         if (buffer.size() >= 1 && (buffer.get(0) == 0x7e) 
                && (buffer.get(buffer.size() - 1) == 0x7f)) {
              out.write(new ArrayList<>(buffer));
             //不应该在此处调用clear()方法
              buffer.clear();
              return true;
          }
     }
    
    

知道了问题所在,于是修改代码

SessionContext ctx = SessionContext.getContext(session, CONTEXT);
List<Integer> buffer = ctx.getBuffer();

        //开始解析数据
        while (in.hasRemaining()) {

            int b = in.get() & 0xff;

            if (buffer.size() > BUFFER_MAX) {
                LogUtil.LogType.errorLog.error("清空缓存");
                ctx.reset();
            }

            //每条记录要确保第首个是 0x7E
            if ((buffer.size() == 0 && b == 0x7E) || (buffer.size() != 0 && buffer.get(0) == 0x7E))
                buffer.add(b);

            //除了判断起止标识,还需要判断buffer长度是否和当前包长度一致
            if (b == 0x7F && buffer.get(0) == 0x7E && buffer.size() > 3
                  && buffer.size() == BinUtil.to16(buffer.get(1), buffer.get(2))) {
                out.write(new ArrayList<Integer>(buffer));
                ctx.reset();
                return true;
            }
        }

于是,再未发现丢数据问题

总结:

总的来说还是自己接触的少,基础知识底子薄,第一次接触这种底层的数据传输着实有点懵逼,总是不停的请教别人,可能感觉有点不好意思,但是我觉得作为程序员,对技术保持好奇心是一个程序员的基本素养,把东西学到手是最重要的。

本文链接:http://blog.keepting.cn/blog//post/tcp_pro_0.html

-- EOF --

Comments