C# 网络流数据丢失

本文关键字:数据 网络 | 更新日期: 2023-09-27 18:34:15

我目前正在做一个网络项目,在那里我制定了一个二进制协议。我的数据包如下所示:[1 字节类型][2 字节索引][2 字节长度][长度字节数据]

这是我接收数据包的代码:

NetworkStream clientStream= Client.GetStream();
while (Client.Connected)
{
    Thread.Sleep(10);
    try
    {
        if (clientStream.DataAvailable)
        {
            byte[] infobuffer = new byte[5];
            int inforead = clientStream.Read(infobuffer, 0, 5);
            if (inforead < 5) { continue; }
            byte[] rawclient = new byte[2];
            Array.Copy(infobuffer, 1, rawclient, 0, 2);
            PacketType type = (PacketType)Convert.ToSByte(infobuffer[0]);
            int clientIndex = BitConverter.ToInt16(rawclient, 0);
            int readLength = BitConverter.ToInt16(infobuffer, 3);
            byte[] readbuffer = new byte[readLength];
            int count_read = clientStream.Read(readbuffer, 0, readLength);
            byte[] read_data = new byte[count_read];
            Array.Copy(readbuffer, read_data, count_read);
            HandleData(read_data, type, clientIndex);
        }
    }
    catch (Exception ex)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("[E] " + ex.GetType().ToString());
        Console.ResetColor();
        break;
    }
}

好吧,一切正常...只要我在 127.0.0.1 上运行它。一旦我尝试远距离测试它,数据包就会以某种方式丢失,并且在我将第一个字节转换为 PacketType 的行上出现溢出异常。另外,如果我尝试将其他值转换为 int16,我会得到非常奇怪的值。

我假设流在到达服务器的途中以某种方式丢失了一些字节,但可以吗?还是只是我在代码中的某个地方犯了一个小错误?

编辑:我现在编辑了代码,现在它读取直到它得到它的 5 个字节。但是我仍然在长距离上得到同样的异常......

NetworkStream clientStream = Client.GetStream();
while (Client.Connected)
{
    Thread.Sleep(10);
    try
    {
        if (clientStream.DataAvailable)
        {
            int totalread = 0;
            byte[] infobuffer = new byte[5];
            while (totalread < 5)
            {
                int inforead = clientStream.Read(infobuffer, totalread, 5 - totalread);
                if (inforead == 0)
                { break; }
                totalread += inforead;
            }
            byte[] rawclient = new byte[2];
            Array.Copy(infobuffer, 1, rawclient, 0, 2);
            PacketType type = (PacketType)Convert.ToSByte(infobuffer[0]);
            int clientIndex = BitConverter.ToInt16(rawclient, 0);
            int readLength = BitConverter.ToInt16(infobuffer, 3);
            byte[] readbuffer = new byte[readLength];
            int count_read = clientStream.Read(readbuffer, 0, readLength);
            byte[] read_data = new byte[count_read];
            Array.Copy(readbuffer, read_data, count_read);
            HandleData(read_data, type, clientIndex);
        }
    }
    catch (Exception ex)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine("[E] " + ex.GetType().ToString());
        Console.ResetColor();
        break;
    }
}

PacketType 是一个枚举:

public enum PacketType
{
    AddressSocks5 = 0,
    Status = 1,
    Data = 2,
    Disconnect = 3,
    AddressSocks4 = 4
}

C# 网络流数据丢失

你在这里做错了很多事情......这么多虫子...甚至从哪里开始...

第一次网络轮询? 真? 这只是当今时代进行网络活动的一种幼稚方式。但我不会对此喋喋不休。

其次,使用这种类型的协议,很容易"不同步",一旦你这样做了,你就没有办法恢复同步。 这通常是通过某种"成帧协议"来实现的,该协议提供了一个独特的字节序列,您可以使用这些字节序列来指示帧的开始和结束,这样,如果您发现自己不同步,您可以读取数据,直到您恢复同步。 是的,您将丢失数据,但如果数据不同步,您已经丢失了数据。

第三,你在这里并没有真正做任何大事,所以我无耻地从这里偷走了"ReadWholeArray"代码,它不是最有效的,但它有效,并且还有其他代码可能会有所帮助:

http://www.yoda.arachsys.com/csharp/readbinary.html

注意:您没有提及如何在另一端序列化长度、类型和索引值。 因此,使用BitConverter可能是错误的事情,具体取决于如何完成。

if (clientStream.DataAvailable)
{
    byte[] data = new byte[5];
    // if it can't read all 5 bytes, it throws an exception
    ReadWholeArray(clientStream, data);
    PacketType type = (PacketType)Convert.ToSByte(data[0]);
    int clientIndex = BitConverter.ToInt16(data, 1);
    int readLength = BitConverter.ToInt16(data, 3);
    byte[] rawdata = new byte[readLength];
    ReadWholeArray(clientStream, rawdata); 
    HandleData(rawdata, type, clientIndex);
}
/// <summary>
/// Reads data into a complete array, throwing an EndOfStreamException
/// if the stream runs out of data first, or if an IOException
/// naturally occurs.
/// </summary>
/// <param name="stream">The stream to read data from</param>
/// <param name="data">The array to read bytes into. The array
/// will be completely filled from the stream, so an appropriate
/// size must be given.</param>
public static void ReadWholeArray (Stream stream, byte[] data)
{
    int offset=0;
    int remaining = data.Length;
    while (remaining > 0)
    {
        int read = stream.Read(data, offset, remaining);
        if (read <= 0)
            throw new EndOfStreamException 
                (String.Format("End of stream reached with {0} bytes left to read", remaining));
        remaining -= read;
        offset += read;
    }
}

我认为问题出在这些行中

int inforead = clientStream.Read(infobuffer, 0, 5);
if (inforead < 5) { continue; }

如果长度小于 5 个字节,您之前读取的数据会发生什么情况? 您应该保存到目前为止已读取的字节并附加下一个字节,以便您可以完全拥有标头

You Read

5 - totalRead。

让 totalRead 等于 5 或更多。当这种情况发生时,你什么都不读,在 1 - 4 的情况下,你读取了那么多任意字节。不是5。然后,您还将丢弃小于 5 的任何结果。

您也可以在偏移量 1 或另一个偏移量处复制,而实际上不知道偏移量。

BitConverter.ToInt16(infobuffer, 3(;

举个例子,关 2 是什么?

因此,如果不是(解码错误(而不是数据的结构,那么除非您更改循环的结构,否则丢失字节的是您而不是 NetworkStream。

在接收时按 justRead 的增量计算总读取,以便您可以处理任何大小的数据以及以正确的偏移量接收数据。