【问题标题】:Most effective way to read from a TCP socket从 TCP 套接字读取的最有效方法
【发布时间】:2013-07-25 03:27:07
【问题描述】:

我有一个到服务器的 TCP 连接,由套接字和流实现。在会话期间,服务器可以发送任意数量的消息 - 我必须阅读并处理所有消息。

我创建了一个线程,它在无限循环中检查和读取数据:

in = socket.getInputStream();
ByteArrayOutputStream baos = null;
byte[] buf = new byte[4096];
while(!isInterrupted()) {
   baos = new ByteArrayOutputStream();
   for(int s; ( s = in.read(buf)) != -1; ) {
       baos.write(buf, 0, s);
       if(in.available() <= 0 ) {
           readChunk(baos.toByteArray());
       }
   }
}

但实际上,它效率不高 - 它使 CPU 处于繁重的负载下,并且不知何故字节与之前的答案粘在一起。

解决这种情况最有效和最优雅的方法是什么?

【问题讨论】:

  • 在不知道您收到的信息类型的情况下很难给出答案。是文本吗?二进制数据?是固定长度吗?您是否也对服务器进行编程?大多数情况下都需要协议。
  • @ja_mesa 它是二进制数据。前四个字节用于写入消息长度,后四个字节用于 CRC32 校验和。

标签: java sockets java-io


【解决方案1】:

删除 available() 调用。 InputStream.available() 不是对流结束的有效检查,它在其文档中这样说。它也永远不能返回负值。 readChunk() 方法也是应该进行阅读的方法。 TCP 中也没有消息,因此使用available() 或任何其他技术来识别它们是无效的。

编辑

你在其他表扬中说你有一个计数前缀。用那个。用DataInputStream.readInt() 读取它,分配一个该大小的byte[] 数组,然后用DataInputStream.readFully(): 填充它

int len = din.readInt();
byte[] message = new byte[len];
din.readFully(message);

【讨论】:

  • 如果没有available(),读取周期 (read()) 永远不会结束,它会返回零。虽然 sniffer 显示消息到此结束。
  • 我重复一遍。 available() 不是检测消息结束的有效技术。
【解决方案2】:

根据您对消息的说法,这是一种方法:

in = socket.getInputStream();
byte[] buff = new byte[4096];
int packLen=0;
int ret=0;
while(!isInterrupted()) {
    int offset=0;
    int bLeft=4;
    // 99% of the times the read will return 4 bytes, 
    // but just in case, put it in a loop.
    while (bLeft > 0) {
        ret = in.read(buff, offset, bLeft);
        if (ret > 0) {
            bLeft-=ret;
            offset+=ret;
        }
        else if (ret == 0) {
            // socket has been closed
        }
        else {
            // soket has an error
        }

    }
    // convert the 4 bytes to an int, depends on the way it's was sent
    // this method is used frecuently
    packLen = (int)((buff[0] & 0xff) << 24) |
                 (int)((buff[1] & 0xff) << 16) |
                 (int)((buff[2] & 0xff) << 8) |
                 (int)(buff[3] & 0xff);

    // if the 4 bytes of the CRC32 is not included in the length, 
    // increment the length
    packLen+=4;
    offset=4;
                if (packLen > 4092)
                {
                   // packet is too big, ignore it or do something else
                   packLen=4092;
                }
    bLeft=packLen;
    // Noew loop until the whole mesage has been read
    while (bLeft > 0) {
        ret = in.read(buff, offset, bLeft);
        if (ret > 0) {
            bLeft-=ret;
            offset+=ret;
        }
        else if (ret == 0) {
            // socket has been closed
        }
        else {
            // soket has an error
        }
    }
    // the readChunk function must be change
    // Need to pass the length of the message. 
    // Is not the buff.length anymore 
    readChunk(buff, packLen+4 /* +4 for the length of the message*/);
}

如果你需要一个 Java CRC32 类我可以给你,它符合 PKZIP 和以太网标准。

编辑: 笔记: 如果数据包长度大于 4096,则此方法将不起作用。

【讨论】:

  • DataInputStream.readInt() 和 readFully() 已经完成了这一切。
【解决方案3】:

TCP 不是面向消息的,它是面向流的。这意味着,如果您发送两条消息 AA 和 BB,则很有可能在不同的场合读取值 AABB、A A B B、A ABB、AAB B、AA BB(其中空格表示不同的读取尝试)。

您将需要自己处理消息大小或消息分隔符,因此不再需要 in.available()。此外,您的代码将相同的数据至少复制 3 次到不同的缓冲区,并考虑在 socket.getInputStream() 上使用 BufferedInputStream。

【讨论】:

  • @bvitaliyg 添加到此答案中,如果效率至关重要,请尝试使用 java nio。见here。
  • 总消息长度在前四个字节中传输。阅读消息时是否可以使用它?
  • 当然。首先读取消息长度,分配该大小的缓冲区,将整个消息读入该缓冲区。如果需要并行执行,请使用阻塞 I/O 和线程。如果您真的希望它具有高吞吐量,请使用现成的消息传递系统,它们很难做到正确。另外 - 您的目标吞吐量是多少?
猜你喜欢
  • 2019-04-02
  • 1970-01-01
  • 2010-10-24
  • 2021-08-16
  • 2016-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多