为什么我从套接字输入流中丢失字节

本文关键字:字节 输入流 套接字 为什么 | 更新日期: 2023-09-27 18:01:25

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

  1. 前4个字节表示一个整数,它定义了要执行的命令类型
  2. 后面的4个字节表示一个整数,它是流
  3. 中剩余字节的长度。
  4. 剩余字节表示有效载荷数据,其总数对应于(2)的结果

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

例如

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

起始数据如下:

  • 命令:1
  • 长度:50
  • 有效载荷:C:'Users'dave'Music' Offaiah-Trouble_ (Club_Mix)。mp3

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

" ' u0001 ' 0 ' ' 02 ' ' 0 ' 0 c: '戴夫'音乐' '用户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());

缓冲区现在包含:"sers'dave'Music'Offaiah-Trouble_(Club_Mix).mp3"

前面缺失的"C:'U"到哪里去了?

为什么我从套接字输入流中丢失字节

所以,我意识到我一直被否决是因为这个问题不够简洁和可重复。因此,我创建了一个小项目来演示问题的这一部分,具有讽刺意味的是,问题消失了。

代码如下:

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个字节。

显然,答案是,它应该暂停在这里,等待工人完成,但我使用async和等待整个所以它让我惊讶,我有这个问题。原因是缺少await和可怕的async void,如下所示。

违规代码:

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)
{
    //...
}