[英]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.