C#NetworkStream数据丢失

时间:2014-12-13 14:53:35

标签: c# .net sockets networkstream

我目前正在开发一个网络项目,在那里我制定了一个二进制协议。我的数据包看起来像这样: [1字节类型] [2字节INDEX] [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
}

3 个答案:

答案 0 :(得分:3)

你在这里犯了很多错事......这么多错误......哪里开始......

首次网络投票?真?这只是在这个时代进行网络活动的一种天真的方式..但我不会对此有所帮助。

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

第三,你真的没有在这里做任何大事,所以我从这里无耻地偷走了“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;
    }
}

答案 1 :(得分:2)

我认为问题出在这些方面

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

如果长度低于5个字节,您之前读取的数据会发生什么变化?您应该保存到目前为止已读取的字节并附加下一个字节,以便您可以完全使用标题

答案 2 :(得分:1)

你读5 - totalRead。

让totalRead等于5或更多。当发生这种情况时,你什么都不读,在1到4的情况下,你会读到许多任意字节。不是5.然后你也丢弃少于5的结果。

您也可以在偏移1或其他偏移处复制而不知道偏移。

BitConverter.ToInt16(信息缓冲,3);

这是一个例子,什么是关闭2?

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

当你收到时,以justRead的增量计算totalRead,这样你就可以处理任何大小的数据,并以正确的偏移量接收它。