【问题标题】:TCP Socket receiving and processing multiple messagesTCP Socket接收和处理多条消息
【发布时间】:2013-01-11 18:02:28
【问题描述】:

我在从 TCP 套接字获取所有数据时遇到了一些麻烦。

在我的服务器中,我正在从这样的套接字读取数据:

        int len;
        byte[] buffer = new byte[2000];
        try {
            this.in = new DataInputStream(this.socket.getInputStream());
            this.out = new DataOutputStream(this.socket.getOutputStream());
            running = true;

            while (running) {
                len = in.read(buffer);
                if (len < 0) {
                    running = false;
                } else {
                    parsePacket(buffer, len);
                }
            }

        } catch (IOException ex) {
            System.out.println("Catch IOException: " + ex);
            ex.printStackTrace();
        } finally {
            try {
                System.out.println("Closing");
                in.close();
                out.close();
                socket.close();
            } catch (IOException ex) {
                System.out.println("Finally IOException: " + ex);
            }
        }

数据包格式是这样的:

[标题][数据][终结者]

  • 标头 --> 标识消息开头的字符序列(没有关于数据包长度的信息);
  • Data --> 被分割成段,如:[Size Seg. 1][数据段。 1][尺寸段。 2][数据段。 2][尺寸段。 3][数据段。 3]....[尺寸段。 N][数据段。 N]
  • 终结者 --> [0x00]

数据的接收速度非常快(有时 200 毫秒或更短),所以有时read(buffer) 会在buffer 中填充如下消息:

  • [HEADER1][DATA1][TERM1] 或,
  • [HEADER1][DATA1][TERM1][HEADER2][DATA2][TERM2].............[HEADER N][DATA N][TERM N] 或,李>
  • [HEADER1][DATA1][TERM1][HEADER2][DATA2][TERM2]................[HEADER N][DAT(最后一条消息不完整)

parsePacket() 方法能够解析具有上述格式的消息,如果接下来有更多消息,它们也将被解析(递归)。但是如果最后一条消息不完整,它不会解析它(我不希望这样,但直到现在我还没有找到合适的解决方案)。

消息中的数据存储在 MySQL 数据库中(使用 JDBC 驱动程序)。消息的每次解析都可能涉及对数据库的多个查询。由于我只使用一个线程来接收、解析和存储数据,因此代码的执行速度并没有达到应有的速度……应该尽快接收和存储数据。

我想讨论的几点:

  • 在不丢失部分消息的情况下获取所有消息的最佳方法是什么?
  • 如何改进接收和存储数据的方式? (应尽快存储数据!)

【问题讨论】:

  • 请记住,TCP 是一种 协议,而不是数据包协议。这意味着您可能不会在一次接听电话中收到所有消息,或者您可能会收到多条消息。

标签: java database sockets tcp


【解决方案1】:

由于 TCP 已经是流协议,因此读取此数据的最简单方法是作为流。我会添加一个监听器来处理事件。

DataInputStream dis = new DataInputStream(new BufferedInputStream(socket.getInputStream()));

try {
   while(true) {
       listener.startOfMessage();
       for(int segSize; (segSize = dis.readInt()) > 0;) {
          byte[] bytes = new byte[segSize];
          dis.readFully(bytes);
          listener.data(bytes);
       }
       int footer = dis.read();
       // check footer ??
       listener.endOfMessage();
   }
} catch(EOFException endOfStream) {
   // handle or ignore
} finally {
   // close everything.
}

当您自己进行缓冲时,您还必须重新组装消息并保留不完整的消息,这在这里很头疼却没有任何好处。

数据接收速度非常快(有时 200 毫秒或更短)

对于您拥有的每个 CPU,200 毫秒大约是 600,000,000 个时钟周期。这对计算机来说是永恒的。 :)

上面的代码应该在 200 毫秒内处理大约 20,000 条消息。如果你需要更多,你可以使用 NIO,但我不认为你需要。

应尽快存储数据!

我怀疑 MySQL 很好,它不是“尽可能快”,但我看不出你所说的不使用它的任何理由。

【讨论】:

  • 但是我在这个解决方案中看到的一个问题是我无法在消息的开头获得消息的长度......
  • 在这种情况下,您必须解析消息。您必须确定接下来是否有大小或终结符。 (查看我的更改以获取一个简单的示例)
  • 我正在尝试这种方法,但我正在使用wireshark查看服务器应用程序何时接收到消息,并且我看到消息与特定数据集之间存在巨大延迟由应用程序接收,并且同一数据集存储在数据库中的时刻......我正在打印从消息开始的时刻到消息被完全解析(和数据存储)的时刻的持续时间。有时我会得到 1 或 2 秒的时间......这太长了。我提醒我在一个线程中完成所有这些。有什么建议吗?
  • 单线程应该不是问题。您是否可以尝试只记录您将生成的 SQL,但不接触数据库以查看在 Java 中需要多长时间?
  • 没有对数据库进行任何查询,我得到 400 毫秒(最大值),但大多数消息都在 ~100 毫秒到 ~200 毫秒之间。为了获得时间,我使用如下代码:startTime = System.currentTimeMillis(); //build message and parse it; endTime = System.currentTimeMillis(); print(endTime-startTime);
【解决方案2】:

你是从buffer 产生String,对吧?在这种情况下,我建议您修改parsePacket 方法的接口并将循环转换为如下内容:

        String tail = "";
        String line = "";
        while (running) {
            len = in.read(buffer);
            if (len < 0) {
                running = false;
            } else {
                line = tail + new String(buffer);
                tail = parsePacket(line, len);
            }
        }

在您的 parsePacket 中,您必须剪切未终止的行尾并从方法中返回它。

【讨论】:

  • 好吧,将 String 类型更改为 byte[] 并以这种方式使用它们:lineBuf = ArrayUtils.addAll(tail, buffer);
【解决方案3】:

TCP 提供 传输服务,而不是数据包服务。为了实现“打包”,协议必须自己构建数据包。在您的情况下,框架是使用 [TERMINTAOR] 标记实现的。在客户端你应该做的是:

  1. 检查您的buffer 是否包含标记。如果没有,则发出 read 以将数据添加到您的 buffer 并返回到第 1 步。
  2. 解析并使用缓冲区中的数据包
  3. 返回步骤 1。

【讨论】:

    【解决方案4】:

    TCP 是一种流协议。它按照写入顺序将写入一端的套接字的所有字节传递到另一端的套接字。它确实保证它们会以与放入它们的大小相同的“块”到达。读取可能会比任何给定的写入获得更多或更少的字节。但是所有的字节都在那里,而且它们的顺序都是正确的。

    解决方案是使用定义消息边界的协议 - 消息终止符、长度标头或 XML 等自描述格式。

    【讨论】:

      【解决方案5】:

      TCP 是一种流协议,它不保证从一个端口到另一个相同块大小的消息的大小。在阅读时,您可能会在一次写入中获得或多或少的字节数。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-08-19
        • 1970-01-01
        • 1970-01-01
        • 2018-03-31
        • 1970-01-01
        • 2016-03-19
        • 1970-01-01
        相关资源
        最近更新 更多