为什么我从Socket InputStream丢失字节?

时间:2016-08-08 18:32:46

标签: c# sockets stream uwp bytestream

我正在创建一个基于套接字的系统,用于在桌面和移动设备之间进行通信。我正在使用一个简单的协议来读取和写入设备之间的数据流,最终以字节结尾:

  1. 前4个字节表示一个整数,它定义了一种要执行的命令
  2. 接下来的4个字节表示一个整数,它是流
  3. 中其余字节的长度
  4. 其余字节表示有效负载数据,其总数对应于(2。)
  5. 的结果

    我能够成功剥离前4个字节并解析命令ok,然后剥离接下来的4个字节并正确解析长度。 当我剥离剩余的字节时,问题出现了,其中一些字节丢失了,而且它们在剩余数据的前面丢失了。

    例如;如果命令是1,并且长度是50,那么流中应该剩下50个字节,但是只有46个字节,它的字节0-3缺失。

    起始数据如下:

    • 命令:1
    • 长度:50
    • 有效负载:C:\ Users \ dave \ Music \ Offaiah-Trouble_(Club_Mix).mp3

    将此转换为字节数组后,我得到:

    “\ U0001 \ 0 \ 0 \ 02 \ 0 \ 0 \ 0℃:\用户\戴夫\音乐\ Offaiah-Trouble_(Club_Mix).MP3”

    (问题 - 为什么第一个整数在它前面得到\ u000而第二个没有?)

    以下是我用于解析此代码的一些代码片段:

    IBuffer inbuffer = new Windows.Storage.Streams.Buffer(4);
    await _socket.InputStream.ReadAsync(inbuffer, 4, InputStreamOptions.None);
    int command = BitConverter.ToInt32(inbuffer.ToArray(), 0);
    

    此时的inbuffer包含:“\ u0001 \ 0 \ 0 \ 0”,BitConverter将此解析为1

    inbuffer = new Windows.Storage.Streams.Buffer(4);
    await _socket.InputStream.ReadAsync(inbuffer, 4, InputStreamOptions.None);
    int length = BitConverter.ToInt32(inbuffer.ToArray(), 0);
    

    inbuffer现在包含:“2 \ 0 \ 0 \ 0”,BitConverter将其解析为“50”

    inbuffer = new Windows.Storage.Streams.Buffer((uint)length);
    await _socket.InputStream.ReadAsync(inbuffer, (uint)length, InputStreamOptions.Partial);
    string path = Encoding.UTF8.GetString(inbuffer.ToArray());
    

    inbuffer现在包含:“sers \ dave \ Music \ Offaiah-Trouble_(Club_Mix).mp3”

    失踪的“C:\ U”从哪里出发?

1 个答案:

答案 0 :(得分:0)

所以,我意识到我一直在投票,因为这个问题并不简洁和可重复。所以我创建了一个小项目来展示问题的这一部分,具有讽刺意味的是,问题就消失了。

以下是代码:

public sealed partial class MainPage : Page
{
    private StreamSocketListener _listener;
    private StreamSocket _client;
    private StreamSocket _socket;
    private HostName _host;
    private int _port = 54321;

    public MainPage()
    {
        InitializeComponent();
        _listener = new StreamSocketListener();
        _listener.ConnectionReceived += async (sender, args) =>
        {
            _socket = args.Socket;
            await Receive();
        };
        _host = NetworkInformation.GetHostNames().FirstOrDefault(x => x.IPInformation != null && x.Type == HostNameType.Ipv4);
    }

    protected async override void OnNavigatedTo(NavigationEventArgs e)
    {
        base.OnNavigatedTo(e);
        await _listener.BindEndpointAsync(_host, $"{_port}");
        await Task.Run(async () =>
        {
            _client = new StreamSocket();
            await _client.ConnectAsync(_host, $"{_port}");

            int command = 1;
            byte[] cmd = BitConverter.GetBytes(command);
            byte[] path = Encoding.UTF8.GetBytes(@"C:\Users\Dave\Music\Offaiah-Trouble_(Club_Mix).mp3");
            byte[] length = BitConverter.GetBytes(path.Length);
            byte[] result = cmd.Concat(length.Concat(path)).ToArray();
            await _client.OutputStream.WriteAsync(result.AsBuffer());
        });
    }

    private async Task Receive()
    {
        while (true)
        {
            IBuffer inbuffer = new Windows.Storage.Streams.Buffer(4);
            await _socket.InputStream.ReadAsync(inbuffer, 4, InputStreamOptions.None);
            int command = BitConverter.ToInt32(inbuffer.ToArray(), 0);
            //The inbuffer at this point contains: "\u0001\0\0\0", and the BitConverter resolves this to 1

            inbuffer = new Windows.Storage.Streams.Buffer(4);
            await _socket.InputStream.ReadAsync(inbuffer, 4, InputStreamOptions.None);
            int length = BitConverter.ToInt32(inbuffer.ToArray(), 0);
            //The inbuffer now contains: "2\0\0\0", and the BitConverter resolves this to "50"

            inbuffer = new Windows.Storage.Streams.Buffer((uint)length);
            await _socket.InputStream.ReadAsync(inbuffer, (uint)length, InputStreamOptions.Partial);
            string path = Encoding.UTF8.GetString(inbuffer.ToArray());
        }
    }
}

如果您创建一个新的空白Universal项目并运行它,您将看到它产生正确的输出。

最终我发现我在这个流阅读器与另一个阅读器之间存在竞争条件。在主项目中,我不会一次读取所有字节。我有一个单独的方法读取和解析“命令”,然后将控制权移到另一个方法,将控制重定向到几个工作方法之一,以服务该特定命令 - 剥离长度,然后剥离其余的有效负载。

问题是'命令阅读器'然后从流中读取另外4个字节,然后工作人员才能读取它的有效负载。

显然答案是它应该暂停并等待工作人员完成,但我正在使用异步并等待整个过程让我感到惊讶,我遇到了这个问题。原因是等待失败,以及可怕的异步无效,如下所示。

违规代码:

private async Task Listen()
{
    while (true)
    {
        //expects a 4 byte packet representing a command
        Debug.WriteLine($"Listening for socket command...");
        IBuffer inbuffer = new Windows.Storage.Streams.Buffer(4);
        await _socket.InputStream.ReadAsync(inbuffer, 4, InputStreamOptions.None);
        int command = BitConverter.ToInt32(inbuffer.ToArray(), 0);

        Debug.WriteLine($"Command received: {command}");
        ParseCommand(command);
    }
}

private async void ParseCommand(int command)
{
    //...
}

...和修改后的版本:

private async Task Listen()
{
    while (true)
    {
        //expects a 4 byte packet representing a command
        Debug.WriteLine($"Listening for socket command...");
        IBuffer inbuffer = new Windows.Storage.Streams.Buffer(4);
        await _socket.InputStream.ReadAsync(inbuffer, 4, InputStreamOptions.None);
        int command = BitConverter.ToInt32(inbuffer.ToArray(), 0);

        Debug.WriteLine($"Command received: {command}");
        await ParseCommand(command);
    }
}

private async Task ParseCommand(int command)
{
    //...
}