【问题标题】:Pass the Length of uncertain Stream to WCF Service将不确定流的长度传递给 WCF 服务
【发布时间】:2016-05-16 06:16:03
【问题描述】:

有没有办法将不确定Stream的长度传递给WCF服务?

Unsertain Stream指的是流

  • 流仅在处理和写入数据后提供其长度。

例如GZipStream

背景

我正在创建一个 WCF 服务从客户端接收 多个 流。

由于 WCF Streaming 只允许消息中包含一个流,因此我决定将所有流连接成一个流并在服务器代码中划分它。

客户端提供的流将包含可变类型的流,例如FileStreamMemoryStream,以及来自 DataTable 序列化的数据和

using (var fileStream = new FileStream(filePath, FileMode.Open))
using (var memoryStream = new MemoryStream())
using (var concatStream = new ConcatenatedStream(fileStream, memoryStream))
{
    client.UploadStreams(concatStream);
}

ConcatenatedStreamc# - How do I concatenate two System.Io.Stream instances into one? - Stack Overflow 中建议的 Stream 实现。

在服务器端,将单个流划分为多个流需要每个流的长度。

由于我想在客户端节省内存,我决定使用 PullStream

PullStream 将根据Read 的要求将Write 缓冲。

但这会导致一个大问题。在开始流式传输之前我无法获得 PullStream 的长度。

任何帮助将不胜感激。

谢谢

【问题讨论】:

  • 也许你可以将所有这些流压缩成一个并在服务器上解压缩它们?
  • 因为我用的是.NET 3.0,ZipArchiver不能用。将文件压缩到本地文件夹需要额外消耗 CPU 和内存,不是吗?但是当没有其他方法时,压缩仍然是我最后的选择。

标签: c# wcf stream


【解决方案1】:

让我们简单点:

    1234563那是一个标准的数据传输模板。这样做,即在每个有效负载之前附加一个标头,您可以向您的服务器提示下一部分将持续多长时间。
  1. 如果在开始将流推送到服务器之前,客户端上没有流的一部分的长度,则必须将标头“插入”到有效负载中。这不是很直观,也不是很有用,但它确实有效。当我在客户端异步准备数据并且第一个缓冲区在长度已知之前准备好时,我使用了这样的东西。在这种情况下,您将需要一个所谓的标记,即在流中的任何位置但在标头之前无法找到的一组字节。

    当第一次完成时,这个场景是三个场景中最难实现的。系好安全带。为了正确地做到这一点,您应该创建流的人工结构。这种结构用于通过网络流式传输视频,称为Network Abstraction Layer 或 NAL,请阅读它。它也被称为 h264 标准中的流格式 AnnexB。您应该从描述标准的领域中抽象出来,这个想法是非常通用的。

    简而言之,有效载荷被分成部分,即所谓的 NAL 单元或 NALU,每个部分都有一个字节序列来标记它的开始,然后是当前 NALU 的类型指示符和长度,然后是 NALU 的有效载荷。出于您的目的,您需要实现两种类型的 NALU:

    • 主要数据负载
    • 元数据

    在您想象您的流应该是什么样子之后,您必须掌握“流编码”的概念。这些是可怕的话,但不要担心。您只需要确保用于标记 NALU 开始的字节序列永远不会在 NALU 的有效负载中遇到。为了实现这一目标,您将实施一些替代策略。 Browse for samples.

    当您考虑完这一点并深入研究之前,请三思而后行。可能场景 3 更适合您。

    如果您确定永远不需要处理流数据的一部分,您可以大大简化场景,即完全跳过流编码并实现如下所示:

客户Stream主体代码:

private byte[] mabytPayload;
private int mintCurrentPayloadPosition;

private int? mintTotalPayloadLength;
private bool mblnTotalPayloadLengthSent;

public int Read(byte[] iBuffer, int iStart, int iLength)
{
    if (mintTotalPayloadLength.HasValue && !mblnTotalPayloadLengthSent)
    {
        //1. Write the packet type (0)
        //3. Write the total stream length (4 bytes).
        ...
        mblnTotalPayloadLengthSent = true;
    }
    else
    {
        //1. Write the packet type (1)
        //2. Write the packet length (iLength - 1 for example, 1 byte is for
        //the type specification)
        //3. Write the payload packet.
        ...
    }
}

public void TotalStreamLengthSet(int iTotalStreamLength)
{
    mintTotalPayloadLength = iTotalStreamLength;
}

服务器流阅读器:

Public void WCFUploadCallback(Stream iUploadStream)
{
    while(!endOfStream)
    {
        //1. Read the packet type.
        if (normalPayload)
        {
            //2.a Read the payload packet length.
            //2.b Read the payload.
        }
        else
        {
            //2.c Read the total stream length.
        }
    }
}
  1. 如果您的上传是不间断的,并且有关流的元数据在有效负载之后很长时间已在客户端上准备好,这种情况也会发生,您将需要两个通道,即一个通道用于有效负载流,另一个通道用于元数据,您的服务器将向客户端回答另一个问题,例如“您刚开始向我发送什么”或“您向我发送了什么”,客户端将在下一条消息中解释自己。

如果您准备好坚持其中一种情况,可以为您提供更多详细信息和/或建议。

【讨论】:

  • 谢谢兹维列夫。情景 2 是我所寻求的。您能解释一下如何在有效负载中插入标头吗?只需在 Stream 的 ctor 中传递 MessageHeader 并在 Read 中写入长度?
  • @Daniel 我扩展了场景 2 的描述。
  • @Daniel 我强烈建议你仔细看看场景 3,你实现起来会简单得多。您只需要使用您创建的 GUID 来唯一地标识每个有效负载,然后再开始推送数据并在 cient 上记忆,然后在每次了解长度时向您的服务器发送一个额外的 WCF 请求,传输长度由 GUID 标识的已知有效负载。两个 WCF 通道,一个用于流式传输负载,一个用于发送元数据。结构非常清晰,非常容易掌握和支撑。
  • 感谢您提供方案详细信息。正如您所提到的,这种情况对我的服务来说太难了,我决定改用场景 3。但是场景 2 让我对流有了新的看法,我也会关注我的项目 :)
  • 我的第一个想法与你的场景3几乎相同。我只是想扩展我的服务,以便每个请求都可以在单个请求中完成。对我来说似乎更好更酷。但事实证明,将流合并为单个并不是一个好主意,所以我会回到我的第一个想法。
猜你喜欢
  • 1970-01-01
  • 2010-11-08
  • 1970-01-01
  • 2010-11-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多