【问题标题】:Problems receiving "large" TCP messages接收“大”TCP 消息的问题
【发布时间】:2020-02-23 22:58:42
【问题描述】:

我在接收大小超过 8000+(可能是 8192)字节的 TCP 消息时遇到问题。

有人知道我做错了什么或错过了什么吗?可能是缓冲区或时间问题?

代码(如下)非常适用于低于此“限制”的消息。

消息的前 64 个字节是包含总消息长度的标头。只有头的前 16 个字节是直接可读的。 字节 0 是 0x46 ('F') 此时不处理字节 1 字节 2 和 3 是标头之后的消息长度。 此时不处理字节 4-7 字节 8-15 全为零。 16-63字节和header后的数据加密。

接收者使用序列号确认每条消息。 当任一部分发送新消息时,它会增加它的序列号。 任何一方只有一条消息同时处于活动状态,在发送下一条消息之前,必须确认前一条消息。

如果序列号不相加,则重置通信。

消息大小不同是正常的,从 64 字节到 10k+。

接收大消息时,第一次消息检查(以“F”开头,长度在范围内)失败并导致返回。

bytes 的内容在检查时是垃圾(return 语句处的断点)。

检查IOHandler->InputBuffer 后发现它包含有效数据(不过这是中断后的一段时间)。

使用Wireshark(过滤器:主机,无arp),我看到对方发送的消息是理智的。它被分成 1460 字节的块和一个更小的块,全部是确认,没有其他消息发生。

下面的代码缺少消息解析和加密/解密,但在其他方面有些完整。 为确保正确维护/处理序列号,并优先考虑对远程部分的确认消息,所有接收和发送均通过此方法完成。

void __fastcall TForm1::TCPServerExecute(TIdContext *AContext)
{
   try
   {
      if(AContext->Connection->IOHandler->InputBufferIsEmpty())
         AContext->Connection->IOHandler->CheckForDataOnSource(100);

      if(!AContext->Connection->IOHandler->InputBufferIsEmpty())
      {
         try
         {
            unsigned short msglen;
            unsigned char* puch = (unsigned char*)&msglen;
            TIdBytes bytes;
            TIdBytes Data;
            int DataLength;
            unsigned char digest[20];

            bytes.Length = 64;
            AContext->Connection->IOHandler->ReadBytes(bytes,64,false);  // read header
            puch[0]= bytes[3];        // Endianess
            puch[1]= bytes[2];
            msglen = msglen & 0x1FF;  // block count of data section each block 16 bytes maximum 640

            if(bytes[0] != 0x46 || msglen > 640)
               return;

            Data.Length = msglen * 16;

            if(msglen)
              AContext->Connection->IOHandler->ReadBytes(Data,DataLength,false);   // read data section

           TMessage Msg = ValidMessage(bytes, Data);
           if(Msg->IsAckMsg)
           {
              TList* list = txList->LockList();
              if(list->Count)
              {
                 for(int i=0; i < list->Count; i++)
                 {
                    TMessage* msg = (TMessage*)list->Items[i];
                    if(Msg->RSeqNo == msg->RSeqNo)
                    {
                       list->Delete(i);
                       delete msg;
                       break;
                    }
                 }
              }
              txList->UnlockList();
           }
           else
           {
              TMessage ACKMsg = Msg->MakeACK();
              TIDBytes senbytes;
              int Msglen = ACKMsg->Prepare(sendbytes);
              AContext->Connection->IOHandler->Write(sendbytes, MsgLen);
              rxList->Add(Msg);
              LogMsg(ACKMsg);
              PostMessage(this->Handle, MSG_UPDATE, NULL, 1);
           }    
         }
         catch(Exception& E)
         {
         }
      }
      else
      {
         TMessage* Msg = NULL;
         TList* list = txList->LockList();
         if(list->Count)
            Msg = (TMessage*)list->Items[0];
         txList->UnlockList();

         if(Msg)
         {
            if(Msg->fMsgSent)
               return;
            TIDBytes bytes;
            int Msglen = Msg->Prepare(bytes);
            AContext->Connection->IOHandler->Write(bytes, MsgLen);
            Msg->fMsgSent = true;
         }
      }
   }
   catch (Exception& E)
   {
   }

Wireshark 总结

Message received correct
1   0.000000    192.168.1.202   192.168.1.72    TCP 342 52000 ? 55496 [PSH, ACK] Seq=1 Ack=1 Win=63080 Len=288      // Command request to remote part
2   0.030413    192.168.1.72    192.168.1.202   TCP 117 55496 ? 52000 [PSH, ACK] Seq=1 Ack=289 Win=11680 Len=63     // Command acknowledged by remote part 
3   0.031360    192.168.1.72    192.168.1.202   TCP 60  55496 ? 52000 [PSH, ACK] Seq=64 Ack=289 Win=11680 Len=1
4   0.031444    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55496 [ACK] Seq=289 Ack=65 Win=63016 Len=0
5   0.050135    192.168.1.72    192.168.1.202   TCP 1514    55496 ? 52000 [PSH, ACK] Seq=65 Ack=289 Win=11680 Len=1460  // Respons from remote part beginning with 0x46 0x01 0x01 0xee
                                                                    // Gives total message length 64 + 494*16 = 7968
6   0.051363    192.168.1.72    192.168.1.202   TCP 1514    55496 ? 52000 [PSH, ACK] Seq=1525 Ack=289 Win=11680 Len=1460
7   0.051424    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55496 [ACK] Seq=289 Ack=2985 Win=64240 Len=0
8   0.053059    192.168.1.72    192.168.1.202   TCP 1514    55496 ? 52000 [PSH, ACK] Seq=2985 Ack=289 Win=11680 Len=1460
9   0.053860    192.168.1.72    192.168.1.202   TCP 1514    55496 ? 52000 [PSH, ACK] Seq=4445 Ack=289 Win=11680 Len=1460
10  0.053910    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55496 [ACK] Seq=289 Ack=5905 Win=64240 Len=0
11  0.055217    192.168.1.72    192.168.1.202   TCP 1514    55496 ? 52000 [PSH, ACK] Seq=5905 Ack=289 Win=11680 Len=1460
12  0.056023    192.168.1.72    192.168.1.202   TCP 722 55496 ? 52000 [PSH, ACK] Seq=7365 Ack=289 Win=11680 Len=668 // Last response chunk
13  0.056091    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55496 [ACK] Seq=289 Ack=8033 Win=64240 Len=0
14  0.056428    192.168.1.202   192.168.1.72    TCP 118 52000 ? 55496 [PSH, ACK] Seq=289 Ack=8033 Win=64240 Len=64  // Respons acknowledged
15  0.057674    192.168.1.72    192.168.1.202   TCP 60  55496 ? 52000 [ACK] Seq=8033 Ack=353 Win=11680 Len=0

Message not received correct
1   0.000000    192.168.1.202   192.168.1.72    TCP 342 52000 ? 55501 [PSH, ACK] Seq=1 Ack=1 Win=62952 Len=288      // Command request to remote part
2   0.034937    192.168.1.72    192.168.1.202   TCP 117 55501 ? 52000 [PSH, ACK] Seq=1 Ack=289 Win=11680 Len=63     // Command acknowledged by remote part
3   0.035910    192.168.1.72    192.168.1.202   TCP 60  55501 ? 52000 [PSH, ACK] Seq=64 Ack=289 Win=11680 Len=1
4   0.035961    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55501 [ACK] Seq=289 Ack=65 Win=62888 Len=0
5   0.056781    192.168.1.72    192.168.1.202   TCP 1514    55501 ? 52000 [PSH, ACK] Seq=65 Ack=289 Win=11680 Len=1460  // Respons from remote part beginning with 0x46 0x01 0x02 0x33
                                                                    // Gives total message length 64 + 563*16 = 9072
6   0.058048    192.168.1.72    192.168.1.202   TCP 1514    55501 ? 52000 [PSH, ACK] Seq=1525 Ack=289 Win=11680 Len=1460
7   0.058083    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55501 [ACK] Seq=289 Ack=2985 Win=64240 Len=0
8   0.059735    192.168.1.72    192.168.1.202   TCP 1514    55501 ? 52000 [PSH, ACK] Seq=2985 Ack=289 Win=11680 Len=1460
9   0.060575    192.168.1.72    192.168.1.202   TCP 1514    55501 ? 52000 [PSH, ACK] Seq=4445 Ack=289 Win=11680 Len=1460
10  0.060604    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55501 [ACK] Seq=289 Ack=5905 Win=64240 Len=0
11  0.062111    192.168.1.72    192.168.1.202   TCP 1514    55501 ? 52000 [PSH, ACK] Seq=5905 Ack=289 Win=11680 Len=1460
12  0.063037    192.168.1.72    192.168.1.202   TCP 1514    55501 ? 52000 [PSH, ACK] Seq=7365 Ack=289 Win=11680 Len=1460
13  0.063061    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55501 [ACK] Seq=289 Ack=8825 Win=64240 Len=0
14  0.063804    192.168.1.72    192.168.1.202   TCP 365 55501 ? 52000 [PSH, ACK] Seq=8825 Ack=289 Win=11680 Len=311
15  0.063805    192.168.1.72    192.168.1.202   TCP 60  55501 ? 52000 [PSH, ACK] Seq=9136 Ack=289 Win=11680 Len=1   // Last response chunk
16  0.063839    192.168.1.202   192.168.1.72    TCP 54  52000 ? 55501 [ACK] Seq=289 Ack=9137 Win=63928 Len=0
No acknowledge as meesage not received correct.

【问题讨论】:

  • 在不知道您尝试实现的实际协议的情况下很难诊断您的问题。消息的实际格式是什么,尤其是 64 字节的标头?而不是使用ReadBytes() 并手动移动字节,您应该使用更高级别的方法,例如TIdIOHandler.ReadInt16(),它会为您处理字节序。而且您绝对不应该像现在这样使用InputBufferIsEmpty()CheckForDataOnSource()。这不是需要这些电话的那种情况。让ReadBytes()(或其他)阻塞,直到收到完整的请求数据
  • 您也没有包含试图在 64 字节标头之后读取消息剩余数据的代码。那么我们怎么知道代码没有留下破坏后续消息读取的字节呢?您需要提供minimal reproducible example,以便我们查看所有内容。
  • 我已经编辑了帖子并添加了更多代码,并且可能 CheckForData... 等更有意义。
  • 您没有显示无法读取的实际数据。如果没有数据包数据,您的 Wireshark 摘要将毫无用处。但我觉得奇怪的一件事是你使用了msglen &amp; 0x1FF,这意味着msglen &gt; 640永远为真,因为msglen 被限制为最大511。您将 msglen 描述为消息长度,但实际上您将其视为块计数器,表示消息中的多个 16 字节块,这似乎与 Wireshark 摘要中的数据包长度不符。您是否有任何关于此协议真正如何工作的文档
  • 我在这段代码中看到了很多逻辑错误,其中最少的是不要捕获和丢弃 Indy 异常,但您还需要使用try..finally 或 RAII 来锁定/解锁您的列表安全,并且您有一些潜在的内存泄漏。

标签: delphi c++builder indy


【解决方案1】:

有时用其他眼睛就足够了。 1FF 掩码是错误的,当用正确的 3FF 替换时,它到目前为止有效。 感谢您敏锐的目光。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-06-17
    • 2011-08-13
    • 1970-01-01
    • 2023-03-16
    • 1970-01-01
    相关资源
    最近更新 更多