繁体   English   中英

Ubuntu套接字编程:数据包在TX和RX之间重新打包

[英]Ubuntu Socket programming : Packets are repackaged between TX and RX

我有2台Ubuntu 14.04 PC。 一个用作服务器,另一个用作客户端。 客户端与服务器建立了TCP连接,服务器将一些数据包发回。 这是服务器上的代码:

发送(SD,PKT,PKT_LEN,MSG_NOSIGNAL);

客户端上的代码也非常简单:

读取(SD,BUF,BUF_SIZE);

如果服务器上的传输间隔开了,我看不到任何问题。 但是,如果服务器正在进行快速传输,则情况看起来很丑。 这是一个示例,说明服务器正在背对背发送8个数据包。

服务器代码显示这8个数据包的大小为:752(字节),713、713、713、396、398、396、396

服务器上的tcpdump捕获4个TX数据包:752(字节),1398、1398、929

客户端上的tcpdump捕获3个RX数据包:752(字节),2796、929

客户端代码显示它仅接收2个数据包,分别为3548字节和929字节。

这样您就可以看到服务器发送的所有字节均已被客户端接收。 然而,分组在传输路径中的各个点处被组合。 我猜这是由于TSO,GSO,GRO等引起的。但是,当这些数据包交付给接收应用程序时,这些优化是否不应该将这些数据包重新组合成正确的格式?

我如何解决这个问题?

TCP经过精心设计,不仅允许而且完全实现您所看到的内容。 这是一个字节流协议。 如果需要消息,则必须通过叠加的应用程序协议自行实现。

我如何解决这个问题?

因此,您使用的是TCP(面向字节流的传输机制),但您希望它具有面向消息的行为。 您无法更改TCP的工作方式(根据设计,它允许按其选择的任意大小的组传输字节,只要字节全部被接收并且以相同的顺序被接收)。 但是您可以在TCP之上添加一个层来模拟面向数据包的行为。

例如,假设您要模拟1000字节“数据包”的传输。 您的发送程序可以首先发送一个固定大小(例如4字节)的标头,该标头将告诉接收方“数据包”将包含多少字节:

size_t myPacketSize = 1000;  // or whatever the size of your packet is 
uint32_t bePacketSize = htonl(myPacketSize);  // convert native-endian to big-endian for cross-platform compatibility
if (send(sd, &bePacketSize, sizeof(bePacketSize), 0) != sizeof(bePacketSize)) 
{
    perror("send(header)");
}

....然后在那之后,您将发送数据包的有效载荷数据:

if (send(sd, packetDataPtr, myPacketSize, 0) != myPacketSize) 
{
    perror("send(body)");
}

接收器将需要接收标头/大小值,然后分配该大小的数组并将有效载荷数据接收到其中。 由于此代码必须正确处理传入的数据,而不管每个recv()调用返回多少字节,所以它比发送代码要复杂一些:

void HandleReceivedPseudoPacket(const char * packetBytes, uint32_t packetSizeBytes)
{
   // Your received-packet-handling code goes here
}

// Parses an incoming TCP stream of header+body data back into pseudo-packets for handling
void ReadPseudoPacketsFromTCPStreamForever(int sd)
{
   uint32_t headerBuf;               // we'll read each 4-byte header's bytes into here
   uint32_t numValidHeaderBytes = 0; // how many bytes in (headerBuf) are currently valid

   char * bodyBuf = NULL;          // will be allocated as soon as we know how many bytes to allocate
   uint32_t bodySize;              // How many bytes (bodyBuf) points to
   uint32_t numValidBodyBytes = 0; // how many bytes in (bodyBuf) are currently valid
   while(1)
   {
      if (bodyBuf == NULL)
      {
         // We don't know the bodySize yet, so read in header bytes to find out
         int32_t numBytesRead = recv(sd, ((char *)&headerBuf)+numValidHeaderBytes, sizeof(headerBuf)-numValidHeaderBytes, 0);
         if (numBytesRead > 0)
         {
            numValidHeaderBytes += numBytesRead;
            if (numValidHeaderBytes == sizeof(headerBuf))
            {
               // We've read the entire 4-byte header, so now we can allocate the body buffer
               numValidBodyBytes = 0;
               bodySize = ntohl(headerBuf);  // convert from big-endian to the CPU's native-endian
               bodyBuf = (char *) malloc(bodySize);
               if (bodyBuf == NULL)
               {
                  perror("malloc");
                  break;
               }
            }
         }
         else if (numBytesRead < 0)
         {
            perror("recv(header)");
            break;
         }
         else
         {
            printf("TCP connection was closed while reading header bytes!\n");
            break;
         }
      }
      else
      {
         // If we got here, then we know the bodySize and now we need to read in the body bytes
         int32_t numBytesRead = recv(sd, &bodyBuf[numValidBodyBytes], bodySize-numValidBodyBytes, 0);
         if (numBytesRead > 0)
         {
            numValidBodyBytes += numBytesRead;
            if (numValidBodyBytes == bodySize)
            {
                // At this point the pseudo-packet is fully received and ready to be handled
                HandleReceivedPseudoPacket(bodyBuf, bodySize);

                // Reset our state variables so we'll be ready to receive the next header
                free(bodyBuf);
                bodyBuf = NULL;
                numValidHeaderBytes = 0;
            }
         }
         else if (numBytesRead < 0)
         {
            perror("recv(body)");
            break;
         }
         else
         {
            printf("TCP connection was closed while reading body bytes!\n");
            break;
         }
      }
   }

   // Avoid memory leak if we exited the while loop in the middle of reading a psuedo-packet's body
   if (bodyBuf) free(bodyBuf);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM