【问题标题】:Sending and receiving images via a socket通过套接字发送和接收图像
【发布时间】:2015-11-21 13:00:58
【问题描述】:

我有一个 C# 桌面应用程序。它连接到我网络上的另一台 PC,它是一个 UWP C# 应用程序。

我正在尝试向我的侦听套接字发送一个或 2 个图像,为了测试这一点,我让侦听套接字将图像发回给我。

问题在于,即使我的服务器接收到所有最初发送回客户端的字节,接收到的图像大小也不相同。

更奇怪的是,有时返回的字节是正确的,我得到了整个图像,当我尝试发送 2 个图像时,第一个是好的,而第二个不是。

然后它将/可以恢复为没有正确发送回的图像。

我认为可能与 async/await 部分有关,我不确定如何。

这是我的服务器代码:

using (IInputStream input = args.Socket.InputStream)
{
    byte[] data = new byte[BufferSize];
    IBuffer buffer = data.AsBuffer();
    uint dataRead = BufferSize;
    while (dataRead == BufferSize)
    {
        await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);
        requestInBytes.AddRange(data.Take((int) buffer.Length));
        dataRead = buffer.Length;
    }
}
var ct = requestInBytes.Count;

然后我跳出标题信息:

int counter = 0;
counter = requestCommand[0].Length;
counter = counter + requestCommand[1].Length;
counter = counter + requestCommand[2].Length;
counter = counter + requestCommand[3].Length;
counter = counter + requestCommand[4].Length;
counter = counter + requestCommand[5].Length;
counter = counter + 6;

现在我提取图像:

var imgBody = new byte[totalBytes.Length- counter];
System.Buffer.BlockCopy(totalBytes, counter, imgBody, 0, imgBody.Length);
byteArray = imgBody;

然后只发回图片:

using (IOutputStream output = args.Socket.OutputStream)
{
    using (Stream response = output.AsStreamForWrite())
    {
        MemoryStream stream = new MemoryStream(byteArray);
        await response.WriteAsync(byteArray, 0, byteArray.Length);
        await response.FlushAsync();
    }
}

这是我的客户端代码:

StringBuilder sb = new StringBuilder();
foreach (var gallery in Shared.CurrentJobGallery)
{
    try
    {
        sb.Clear();
        sb.Append(GeneralTags.ACTION_ADD);
        sb.Append(Shared.DLE);
        sb.Append("GALLERY");
        sb.Append(Shared.DLE);
        sb.Append(Shared.CurrentClientId);
        sb.Append(Shared.DLE);
        sb.Append(gallery.Title);
        sb.Append(Shared.DLE);
        sb.Append(gallery.Description);
        sb.Append(Shared.DLE);
        sb.Append(jobRef);
        sb.Append(Shared.DLE);
        byte[] galleryHdr = Encoding.UTF8.GetBytes(sb.ToString());
        byte[] byteArray = new byte[galleryHdr.Length + gallery.ImageData.Length];

        Buffer.BlockCopy(galleryHdr, 0, byteArray, 0, galleryHdr.Length);
        Buffer.BlockCopy(gallery.ImageData, 0, byteArray, galleryHdr.Length, gallery.ImageData.Length);
        List<byte> requestInBytes2 = new List<byte>();
        System.Diagnostics.Debug.WriteLine("SENT: " + gallery.ImageData.Length.ToString());
        using (TcpClient clientSocket = new TcpClient())
        {
            await clientSocket.ConnectAsync(GeneralTags.RASPBERRY_PI_IP_ADDRESS, GeneralTags.RASPBERRY_PI_PORT);
            using (NetworkStream serverStream = clientSocket.GetStream())
            {
                List<byte> requestInBytes = new List<byte>();

                serverStream.Write(byteArray, 0, byteArray.Length);
                serverStream.Flush();
                int i;
                Byte[] bytes = new Byte[1024];
                do
                {
                    i = serverStream.Read(bytes, 0, bytes.Length);
                    byte[] receivedBuffer = new byte[i];
                    Array.Copy(bytes, receivedBuffer, i);

                    requestInBytes2.AddRange(receivedBuffer);
                } while (serverStream.DataAvailable);

            }
        }

        using (MemoryStream ms = new MemoryStream())
        {
            System.Diagnostics.Debug.WriteLine("BACK: " + requestInBytes2.Count.ToString());
            ms.Write(requestInBytes2.ToArray(), 0, requestInBytes2.ToArray().Length);
            Shared.ViewImage(Image.FromStream(ms, true));
        }
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
}

【问题讨论】:

  • 不要只阅读和发送图像。使用一些协议来做到这一点,比如 RTP。这样,您将能够跟踪和记录发送的数据包。您将能够找到网络上是否有任何丢包。
  • @dgate 我相信你是对的,但是你有 C# 上下文中的代码示例吗?谢谢
  • 好吧,我不是 C# 程序员,但您可以查看 RFC 3550。或者您可以在每个数据包之前添加一个序列号,并在接收方检查客户端是否收到所有数据包。
  • @dgate 我确实进行了简短的搜索,但没有很多。但是,您能看出我的代码有什么问题并提出改进建议吗?我通常使用套接字来传输图像并且从来没有遇到过问题。通常我会使用线程。但最新的事情似乎是使用 await 和 async 如果我想保持最新状态,我需要使用这些东西。我怀疑这就是我的代码不起作用的原因。我不相信有一个强有力的案例(在这个例子中)来切换协议,因为 TCP 甚至 UDP 以前都是依赖的。 :)
  • 我不熟悉 C# 编程。但是我可以看到您正在一次发送整个图像。而不是找出您的 MTU 并确定 MAX 数据包大小。然后读取并发送以序列号作为标头的 MAX_SIZE 数据包发送。在接收方使用序列号对接收到的数据包进行排序。如果任何序列号丢失,那么您就会丢失数据包。这种方式是流式传输的最佳做法。

标签: c# sockets uwp raspberry-pi2 iot


【解决方案1】:

您的问题是 TCP 套接字基于 streams,而不是 packets。确实,“在线”上的一切都是一个数据包,但是当您使用 TCP 时,您无法控制数据如何拆分为数据包或重新组合成一个流。

尤其是这行代码不正确:

await input.ReadAsync(buffer, BufferSize, InputStreamOptions.Partial);

根据the docs,您必须使用从ReadAsync 返回的缓冲区。另请注意,此缓冲区可能是部分图像,由您的代码 来检测这种情况,必要时阅读更多内容,并将这些块附加在一起。此外,缓冲区可能包含一个图像的一部分和下一个图像的一部分;检测并正确处理它也取决于您的代码。

出于这个原因,大多数 TCP 应用程序使用某种形式的 message framing(在我的博客中有更详细的描述)。请注意,要做到这一点非常困难。

我强烈建议您使用 SignalR 而不是原始 TCP 套接字。 SignalR 为您处理消息框架,并且它能够自托管(即,它不需要 ASP.NET)。

【讨论】:

  • 嗨,斯蒂芬。感谢您的回答。它是有益的。您提到的代码行是来自 MSDN (somewhere) for UWP IoT .devices 的示例代码。应该是什么?像往常一样,文档不会为像我这样的简单人提供一个干净简单的示例。我已将客户端代码更改为:while (requestInBytes2.Count
  • 我以前使用过 SignalR,虽然我喜欢它,但它在传输字节数组时确实存在问题,而问题在于它将它转换为 base64,这会“增加”数据。我不相信每个问题都应该用 signalR 来解决。有一段时间,信号器不存在。如果有的话,我会使用 WebSockets,但我没有将 uwp 用于 Raspberry Pi 的选项。我以前曾多次成功使用过套接字,但在这种情况下它似乎不起作用:(
  • @AndrewSimpson:MSDN 套接字示例都很糟糕。大错特错。 SignalR 不是万能的解决方案,但是(如果你可以在两边都使用它)它比原始套接字要容易很多。
  • 嗨,我并不是要暗示 MSDN 都很糟糕。但有时他们没有给出示例代码或/如果他们给出了一个完整的应用程序,我只想要一个简单的例子。意见问题。 SignalR 更容易(如果可以使用的话)但是,a)它在这里没有识别出我的问题,并且 b)我更喜欢使用二进制而不是 base64。我的问题显示了我遇到的问题,我只想知道问题可能是什么?我有另一个使用 udp 的应用程序,它每秒上传 30 帧的 jpeg,除了一些丢帧之外根本没有问题。非常感谢您的回答
  • @AndrewSimpson:问题是您需要使用消息框架。 SignalR 会为您执行此操作,但由于您使用的是原始套接字,因此您必须自己执行此操作。
猜你喜欢
  • 1970-01-01
  • 2010-10-19
  • 1970-01-01
  • 2013-12-31
  • 2012-02-12
  • 2013-09-11
  • 2014-07-17
相关资源
最近更新 更多