【问题标题】:Why does my TCP application misses some packets when sent rapidly为什么我的 TCP 应用程序在快速发送时会丢失一些数据包
【发布时间】:2014-02-04 15:07:09
【问题描述】:

我正在使用异步套接字在 C# 中为每个服务器应用程序编写多个客户端。连接上的每个客户端发送 10 个项目。问题是,当快速启动大量客户端时,似乎每个客户端发送的项目少于 10 个,有时它什么也不发送,服务器只记录它们的连接。

数据包结构是前 4 个字节是一个 int,其中包含数据大小。这是服务器代码的一部分。每个连接的客户端都有自己的接收缓冲区,BeginReceive 应写入该缓冲区。

private void Recieve(IAsyncResult iar) //Called when socket receives something.
{
    Socket server_conn = (Socket)iar.AsyncState;

    if (!SocketConnected(server_conn))
    {
        server_conn.Close();
        logthis("Client Disconnected");
        return;
    }
    int n = server_conn.EndReceive(iar);  //Stop Receiving and parse data, n is number of bytes received

    ClientData asdf = null;
    foreach (ClientData cls in clientlist)
    {
        if (server_conn.RemoteEndPoint == cls.clientsock.RemoteEndPoint) //Who sent this data
        {
            asdf = cls; //cls is who sent this data

            //Start a new thread and pass received bytes to it in order to be parsed
            var t = new Thread(() => parse(cls, n,cls.recvbuffer));
            t.Start();
            Thread.Sleep(100);
            break;
        }
    }

    asdf.recvbuffer = new byte[1024];      //Clear buffer of client
    server_conn.BeginReceive(asdf.recvbuffer, 0, asdf.recvbuffer.Length, SocketFlags.None, new AsyncCallback(Recieve), server_conn); //Start receiving again

}

private void parse(ClientData theclient, int nobytesreceived, byte[] bytesreceived)
{
    ClientData cls = theclient;
    int n = nobytesreceived;
    byte[] receivedbytes = bytesreceived;


    lock(s)
    {
    if (!cls.dataphase)  //If there's no fragmented packets still waiting to be read
    {
        cls.dataphase = true;   
        byte[] sizeinbytes = new byte[4];
        for (int i = 0; i < 4; i++)
        {
            sizeinbytes[i] = receivedbytes[i];
        }
        int size = BitConverter.ToInt32(sizeinbytes, 0);  //Read first four bytes of packet to get size of data

        if ((n - 4) == size)  //If received number of bytes - 4 is equals to datasize
        {
            byte[] payload = new byte[size];
            Array.Copy(receivedbytes, 4, payload, 0, (n - 4)); //Copy data to separately to payload array to be displayed to user
            logthis(cls.clientsock.RemoteEndPoint.ToString());
            logthis(Encoding.ASCII.GetString(payload));
            cls.dataphase = false;       //packet read successfully 
        }
        else if ((n - 4) < size) //If received amount of bytes is less than data size (fragmented data)
        {
            cls.data = new byte[size];
            for (int i = 4; i <= n - 4; i++)
            {
                cls.data[i - 4] += receivedbytes[i];
            }

            cls.datasize = size; //And cls.dataphase will remain true so it can be handled correctly the next time we receive something from same client
        }
        else if((n-4) > size)  //If received amount of bytes is bigger than data (lumped packets)
        {
            byte[] payload = new byte[size];
            byte[] otherpacket = new byte[(n - 4) - size];

            for(int i = 0; i < size; i++)
            {
                payload[i] += receivedbytes[i + 4];

            }

            logthis(cls.clientsock.RemoteEndPoint.ToString());
            logthis(Encoding.ASCII.GetString(payload));
            Array.Copy(receivedbytes, (size + 4), otherpacket, 0, ((n - 4) - size));
            receivedbytes = new byte[(n - 4) - size];
            receivedbytes = otherpacket;
            cls.dataphase = false;
            parse(cls, ((n - 4) - size), receivedbytes); //Send rest of packet to read again
        }

    }
    else
    {
        //not implemented, supposed to handle fragmented packets
        if (n >= cls.datasize)
        {

        }
        else if (n < cls.datasize)
        {

        }

    }

}

}

【问题讨论】:

  • 那里的比赛条件非常糟糕。如果asdf.recvbuffer = new byte[1024]; 发生在parse(cls, n,cls.recvbuffer) 之前,您将向parse 发送一个空缓冲区。
  • 是的,我刚才在修改代码时注意到了这一点,我现在在调用之前将 recvbuffer 复制到另一个字节数组并将其传递给 parse()。现在我只从第一个连接的客户端获得前 10 项,而其他客户端则什么都没有:/
  • 代码太多了。我不会花时间去理解它。
  • 我本可以将 FIFO 队列用于缓冲区,这会使代码更加简单易读,但现在为时已晚。

标签: c# sockets tcp


【解决方案1】:

你的问题来自于

else
{
    //not implemented, supposed to handle fragmented packets

我会把钱放在你正在点击 else 语句并丢失数据的事实上。一旦您没有完整读取从您的读取函数返回的一个数据包或两个数据包(这比您想象的要普遍得多),您的客户端现在就卡在cls.dataphase = true; 并且永远不会退出它。

【讨论】:

  • +1 并注意应用程序中没有数据包,更不用说碎片数据包了。 TCP 是一种字节流协议。要么你有足够的字节用于你当前的目的,要么你没有。
  • @Ejp 是的,我不想混淆 OP,但你是绝对正确的。他使用的是“数据包”这个词,而“消息”这个词更合适,我只是想使用相同的术语。
  • 我会尝试实现该部分,看看它是否修复它。我没有打赌问题可能来自它,因为每个项目的尺寸都非常小,而且它们总是放在一个单独的消息/数据包中。 (每个 24 字节,共 240 字节)
  • 如果消息很小,您可能会在一个 Read 呼叫中收到 2 或 3 条消息。因此,当它处理每条消息时,您会点击您的else if((n-4) &gt; size) 几次,然后如果未完全读取最后一条消息,则它会点击else if ((n - 4) &lt; size) 块,现在您的客户端无法再发送数据。从您的评论“现在我只从第一个连接的客户端获得前 10 个项目,而从其他客户端什么都没有”您可能会收到 10 1/2 条消息,它会处理前 10 个和11 日失败。
  • 每次输入 parse 时都会计算大小。如果最后一条消息的长度小于 4 个字节并导致计算错误的大小,则会出现问题。这是我需要处理的另一个错误。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-14
  • 1970-01-01
  • 2010-10-04
  • 1970-01-01
相关资源
最近更新 更多