【问题标题】:InputStream reads extra bytes which are not exist in sourceInputStream 读取源中不存在的额外字节
【发布时间】:2016-03-24 18:53:35
【问题描述】:


我有接收从客户端应用程序发送的 RTMP 数据包的 Java 服务器。服务器使用InputStream 读取packet header,识别packet body 的大小,然后使用size 创建byte array,然后从InputStream 中的array 中读取body问题是:接收到的字节集被修改 - 有必要的字节(存在于源中)与额外的字节在源中不存在数据包(我通过 WireShark 观察源数据包的内容,并将它们与我在服务器上收到的字节进行比较)。
这些额外的字节是 0xc6 字节,顺便说一下定期会面...
它看起来像这样:
Source: ... 75 f1 f5 55 73 .... fc a9 47 14 ... 40 ca d5 75 ... fe 30 a7
Received: ... 75 f1 f5 55 73 c6 .... fc a9 47 14 c6 ... 40 ca d5 75 c6 ... fe 30 a7
... - 表示“这里有一些字节数”
结果,我无法接收到必要的数据,因为它被拉伸了,比我从 rtmp header 收到的 body size 大。最重要的是,修改后的数据不是我必须收到的!
我的问题是:如何修复? InputStream 怎么了?为什么它将那些 0xc6 字节插入接收数组?
我知道我可以简单地解析接收到的数组并排除那些额外的字节,但这是一个糟糕的解决方案,因为速度和性能是必需的(并且,在这种情况下,不清楚它是来自源的额外字节或字节,没有比较整个数组)...

enter code here
public static void getRtmpPacket(InputStream in) throws Exception {

    byte[] rtmpHeader = new byte[8];
    byte[] rtmpBody;
    int bodySize = 0;

    //reading rtmp header:
    in.read(rtmpHeader);
    //reading the body size. This method works fine
    bodySize = Server.bigEndianBytesToInt(rtmpHeader, 4, 3);
    rtmpBody = new byte[bodySize];
    in.read(rtmpBody);
    //printing received data:
    System.out.println("Packet:");
    System.out.println("Body size: " + bodySize);
    System.out.print(bytesToString(rtmpHeader) + " ");
    System.out.print(bytesToString(rtmpBody));
    System.out.println();

}

【问题讨论】:

  • 请输入代码。最好简化为minimal reproducible example
  • InputStream 从不这样做。如果是这样,则意味着源的绑定没有您想象的那么好。
  • 这不可信。显然,您在他的 cod 中存在您尚未发布的错误,例如 bytesToString()

标签: java sockets stream byte inputstream


【解决方案1】:

根据 RTMP 规范,它的行为正常。您需要对传入的数据进行“分解”,因此在一次 read() 中一次读取所有数据是行不通的。

类似的东西(伪代码):

int remaining = payloadSize;
int totalRead = 0;
int totalReadForChunk = 0;
while (true) {
  int num = read(buf, 0, min(remaining, chunkSize - totalReadForChunk))
  if (num < 0) break;  // i/o error
  appendData(<buf>, 0, num)
  totalReadForChunk += num
  remaining -= num
  if (remaining == 0) break;  // end of payload

  if (totalReadForChunk == chunkSize) {
     totalReadForChunk = 0;
     // read the chunk header (it's not neccessarily 0xc6)
     int header = read()
     if (header != currentStreamEmptyHeader) {  // 0xc6
       // ... parse the new rtmp message according to header value
       // (usually invoke the upper-level message reading method "recursively")
     }
  }
}

【讨论】:

  • 谢谢你的例子。请纠正我,如果我错了:据我了解,您的意思是我需要逐块读取所有数据包(例如,如果块大小为 128 字节,则读取 128 字节,然后再执行一次直到体型结束)?对不起,愚蠢的问题,我是这个领域的新手......
  • 是的,尝试读取整个块(或者如果剩余的预期数据较小,则小于该块),并且在每个完整块之后,从更高层重复该过程(例如,就好像你开始读取一个新消息,可能在同一个流中,也可能来自另一个流,具体取决于您将读取的下一个字节,即下一个块头)。这意味着您可以在完成读取当前流的有效负载之前收到来自其他流的一些其他消息。
  • 感谢您的解释。我几乎完成了它,但我仍然有这个过程的一些不清楚的部分。请帮助我理解它们。客户端将第一个视频包发送到服务器(从 Flash 播放器)。从该数据包的标题中,我得到了正文的大小-> 大约 28000 字节。以前我通过命令“Set Chunk Size 4096”(从服务器发送)将块大小设置为 4096。但是,当我尝试读取该正文时,它由 128 大小的块分隔(每个块以 0xc6 字节结尾)。在这一点上,我并不清楚我下一步该做什么(我将在下一条评论中解释)
  • 我的有效载荷大小将是主体大小(在数据包头中指定)。但是我不明白您在示例的 cmets 中是什么意思。 if (header != currentStreamEmptyHeader) { // 0xc6 // ... parse the new rtmp message according to header value - 当我读取数据包的前 128 字节块(大约 28000 字节)时,我会遇到 0xc6。根据评论,在这种情况下我需要调用上层消息读取方法,但是0xc6之后的那些字节不包含适当的头格式......它们只是那28000字节的剩余部分。
  • 我可以逐块读取整个正文大小,然后将所有内容聚集在一起(之前读取的标题和逐块读取的正文)。问题是:我是否需要将 0xc6 字节附加到每个块的末尾(尤其是当我将它们发送到其他客户端/对等方时)
【解决方案2】:

您可能应该看到(并使用)Red5 Media Server 的代码和其他实现 RTMP 协议的开源解决方案。

【讨论】:

    【解决方案3】:

    InputStream.read(byte[]) 只保证读取一个字节,它返回长度为实际读取长度的int

    in.read(rtmpHeader); // might read 1, 2, 3, .. 8 bytes.
    //reading the body size. This method works fine
    bodySize = Server.bigEndianBytesToInt(rtmpHeader, 4, 3);
    rtmpBody = new byte[bodySize];
    in.read(rtmpBody); // might read 1, 2, 3, ... bodySize bytes.
    

    如果您不检查实际长度,并假设 byte[] 已满,则在调用 read() 之前您会得到任何字节。

    使用 DataInputStream 可以获得您想要的内容

    DataInputStream dis = new DataInputStream(in);
    
    int len = dis.readInt(); // read an int in big endian.
    byte[]] bytes = new byte[len];
    dis.readFully(bytes); // read the whole byte[] or throw an IOException.
    

    【讨论】:

    • 你写的一切都是正确的,但它似乎并不能解释 OP 的主要主张和担忧:在他的单一 read() 打算输入整个消息有效负载时,他得到了混杂的虚假字节预期的(不只是在最后)。
    • @JohnBollinger 不清楚虚假字节是在标头、正文还是末尾。格式可能不是 OP 所期望的,例如它可能是一个 9 字节的标头....
    【解决方案4】:

    问题已解决。
    那些额外的0xc6字节是RTMP数据包的分块字节,从WireShark看不到。
    不仅如此,收到的 header 说的是实际的 body 大小,WireShark 会“确认”它,但实际上 body 的大小会更大,应该计算一下。

    【讨论】:

    • RTMP 分块位于传输的较低级别,因此在分解实际的 RTMP 消息负载时它应该具有优先级。请注意,它会随着每条新的 RTMP 消息而重置。不要忘记在回复数据时也将其添加回来,您的有效负载有时可能会大于当前块大小,并且您将不知道为什么最终会关闭连接(服务器无法解析您的消息)。
    • @AdrianCrețu 你能给我一些这个过程的例子吗?我真的需要关于分块的详细信息(在服务器上正确接收这个块,并发送给其他对等方),因为我有一些问题,官方规范还不够
    猜你喜欢
    • 2023-03-30
    • 1970-01-01
    • 1970-01-01
    • 2011-11-26
    • 2015-07-11
    • 2020-03-11
    • 2015-04-02
    • 2015-03-03
    • 1970-01-01
    相关资源
    最近更新 更多