【问题标题】:Problem calculating byte length when sending data over sockets通过套接字发送数据时计算字节长度的问题
【发布时间】:2011-07-30 11:07:31
【问题描述】:

我正在编写一个客户端/服务器应用程序。使用环回地址连接时它似乎运行良好,但是当通过 LAN 或 Internet 连接时,我遇到了一个非常奇怪的问题,我无法弄清楚。

服务器和客户端之间发送的所有数据都包含在一个名为“DataPacket”的类中。然后将此类序列化为二进制,以便可以使用套接字发送。

在发送数据包之前,会附加一个 4 字节的标头,其中包含数据包的字节长度,以便接收方知道在反序列化之前需要多少字节。问题是,在某些时候,接收方认为它正在等待比标头中实际发送的数据多得多的数据。有时这个数字是数百万字节,这会导致严重的问题。

这是发送数据的方式:

public override void Send(DataPacket packet)
{
    byte[] content = packet.Serialize();
    byte[] header = BitConverter.GetBytes(content.Length);

    List<Byte> bytes = new List<Byte>();
    bytes.AddRange(header);
    bytes.AddRange(content);

    if (socket.Connected)
    {
        socket.BeginSend(bytes.ToArray(), 0, bytes.Count, SocketFlags.None, OnSendPacket, socket);
    }
}

在服务器端,每个客户端都分配有一个 StateObject - 这是一个保存用户套接字、缓冲区和到目前为止已接收到的任何不完整数据的类。

StateObject 类的外观如下:

public class StateObject
{
    public const int HeaderSize = 4;
    public const int BufferSize = 8192;

    public Socket Socket { get; private set; }
    public List<Byte> Data { get; set; }
    public byte[] Header { get; set; }
    public byte[] Buffer { get; set; }

    public StateObject(Socket sock)
    {
        Socket = sock;
        Data = new List<Byte>();
        Header = new byte[HeaderSize];
        Buffer = new byte[BufferSize];
    }
}

StateObject 类是这样使用的:

private void OnClientConnect(IAsyncResult result)
{
    Socket serverSock = (Socket)result.AsyncState;
    Socket clientSock = serverSock.EndAccept(result);

    StateObject so = new StateObject(clientSock);
    so.Socket.BeginReceive(so.Buffer, 0, StateObject.BufferSize, SocketFlags.None, OnReceiveData, so);
}

当从客户端接收到一些数据时,会调用 EndReceive。如果 StateObject.Data 为空,则假定这是新数据包的第一部分,因此检查标头。如果接收到的字节数小于标头中显示的大小,那么我再次调用 BeginReceive,重新使用相同的 StateObject。

这是 OnReceiveData 的代码:

private void OnReceiveData(IAsyncResult result)
{
    StateObject state = (StateObject)result.AsyncState;

    int bytes = state.Socket.EndReceive(result);
    if (bytes > 0)
    {
        state.ProcessReceivedData(bytes);

        if (state.CheckDataOutstanding())
        {
            //Wait until all data has been received.
            WaitForData(state);
        }
        else
        {
            ThreadPool.QueueUserWorkItem(OnPacketReceived, state);

            //Continue receiving data from client.
            WaitForData(new StateObject(state.Socket));
        }
    }
}

如上所示,首先我调用 state.ProcessReceivedData() - 这是我从数据中分离标题的地方,然后从缓冲区中移动数据:

public void ProcessReceivedData(int byteCount)
{
    int offset = 0;

    if (Data.Count == 0)
    {
        //This is the first part of the message so get the header.
        System.Buffer.BlockCopy(Buffer, 0, Header, 0, HeaderSize);
        offset = HeaderSize;
    }

    byte[] temp = new byte[byteCount - offset];
    System.Buffer.BlockCopy(Buffer, offset, temp, 0, temp.Length);
    Data.AddRange(temp);
}

然后我调用 CheckDataOutstanding() 来比较接收到的字节数和标头中的大小。如果这些匹配,那么我可以安全地反序列化数据:

public bool CheckDataOutstanding()
{
    int totalBytes = BitConverter.ToInt32(Header, 0);
    int receivedBytes = Data.Count;
    int outstanding = totalBytes - receivedBytes;

    return outstanding > 0;
}

问题在于,不知何故,标头中的值与发送时的值不同。这似乎是随机发生的,有时在服务器上,有时在客户端上。由于网络上调试服务器和客户端的随机性和普遍困难,我发现几乎不可能解决这个问题。

我在这里遗漏了什么明显或愚蠢的东西吗?

【问题讨论】:

  • 发布的代码只能对一个数据包正常工作。缺少重置 StateObject 所需的代码,尤其是清空数据。足以导致所描述的问题。

标签: c# sockets


【解决方案1】:

如果你只得到标题的 part 会发生什么?也就是说,假设您在第一次读取时获得了三个字节而不是四个或更多?您的 ProcessReceivedData 函数需要考虑这一点,并且在您至少有四个字节之前不要尝试提取标头大小。

【讨论】:

  • 虽然你是对的,这是我忽略的问题,并且肯定需要考虑,但我认为这不是问题的原因。我只是尝试在 ProcessReceivedData 中添加一个少于 4 个字节的断点检查,并且在问题发生时它从未受到影响。
猜你喜欢
  • 2011-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-10
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
相关资源
最近更新 更多