关键是您必须意识到计算机中的所有内容都只是一个字节数组(或字或双字)。
ZEN MASTER MUSTARD 坐在他的办公桌前,盯着他的显示器,盯着看似随机字符的复杂图案。一名学生走近。
学生:师父?我可以打断吗?
芥末禅师:你已经回答了你自己的询问,我的孩子。
人:什么?
ZMM:通过问你关于打断我的问题,你打断了我。
S:哦,对不起。我有一个关于从一个地方到另一个地方移动不同大小的结构的问题。
ZMM:如果是这样,那你应该请教擅长这种事情的大师。我建议您拜访一下 DotPuft 大师,他在将大型金属结构(例如跟踪雷达)从一个地方移动到另一个地方方面拥有丰富的知识。Master DotPuft 还可以使羽毛重量应变计的最轻微元素随着鸽子呼吸的力量而移动。右转,然后在您到达 hi-bay 的门时左转。DotPuft 大师住在那里。
S:不,我的意思是在计算机内存中移动不同大小的大型结构。
ZMM:如果你愿意,我可以帮助你。描述你的问题。
S:具体来说,我有一个 ac 函数,我想接受几种不同类型的结构(它们将代表不同类型的数据包)。所以我的结构数据包将作为 void* 传递给我的函数。但是在不知道类型的情况下,我无法施放它们,或者真的做很多事情。我知道这是一个可以解决的问题,因为来自 socket.h 的 sento() 正是这样做的:
ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr,socklen_t dest_len);
sendto 将被称为:
sendto(socketAddress, &myPacket, sizeof(myPacket), Other args....);
ZMM:你有没有向MANTA禅师描述过你的问题!?
S:是的,他说,“它只是一个指针。C 中的一切都是指针。” 当我请他解释时,他说,“滚,滚,滚出我的办公室。”
ZMM:真的,你已经和大师谈过了。这对你没有帮助吗?
S:嗯,呃,没有。然后我问禅师Max。
ZMM:他很聪明。他的建议对你有用吗?
S:没有。当我问他关于 sendto() 的问题时,他只是在空中挥舞着拳头。它只是一个字节数组。”
ZMM:确实,Zen Master Max 有 tau。
S:是的,他有 tau,但我该如何处理 void* 类型的函数参数?
ZMM:要学习,必须先忘却。关键是您必须意识到计算机中的所有内容都只是一个字节数组(或字或双字)。一旦有了指向缓冲区开头的指针和缓冲区的长度,就可以将其发送到任何地方,而无需知道缓冲区中放置的数据类型。
S:好的。
ZMM:考虑一串可读文本。“你打算建一座穿云的塔?先打下谦卑的根基。” 它有 82 个字节长。或者,如果使用邪恶的 Unicode,也许是 164。防范 Unicode 的谎言!我可以通过提供指向包含字符串的缓冲区开头的指针以及缓冲区的长度来将此文本提交给 sendto(),如下所示:
char characterBuffer[300]; // 300 bytes
strcpy(characterBuffer, "You plan a tower that will pierce the clouds? Lay first the foundation of humility.");
// note that sizeof(characterBuffer) evaluates to 300 bytes.
sendto(socketAddress, &characterBuffer, sizeof(characterBuffer));
ZMM:请注意,字符缓冲区的字节数是由编译器自动计算的。任何变量类型所占用的字节数都是一种叫做“ size_t
”的类型。它可能等同于“ long
”或“ unsinged int
”类型,但它取决于编译器。
S:好吧,如果我想发送一个结构怎么办?
ZMM:那么,让我们发送一个结构体。
struct
{
int integerField; // 4 bytes
char characterField[300]; // 300 bytes
float floatField; // 4 bytes
} myStruct;
myStruct.integerField = 8765309;
strcpy(myStruct.characterField, "Jenny, I got your number.");
myStruct.floatField = 876.5309;
// sizeof(myStruct) evaluates to 4 + 300 + 4 = 308 bytes
sendto(socketAddress, &myStruct, sizeof(myStruct);
S: 是的,这非常适合通过 TCP/IP 套接字传输东西。但是接收功能差怎么办?如何判断我发送的是字符数组还是结构?
ZMM:一种方法是枚举可能发送的不同类型的数据,然后将数据的类型与数据一起发送。禅师称之为“元数据”,即“关于数据的数据”。您的接收函数必须检查元数据以确定正在发送的数据类型(结构、浮点数、字符数组),然后使用此信息将数据转换回其原始类型。首先,考虑传输函数:
enum
{
INTEGER_IN_THE_PACKET =0 ,
STRING_IN_THE_PACKET =1,
STRUCT_IN_THE_PACKET=2
} typeBeingSent;
struct
{
typeBeingSent dataType;
char data[4096];
} Packet_struct;
Packet_struct myPacket;
myPacket.dataType = STRING_IN_THE_PACKET;
strcpy(myPacket.data, "Nothing great is ever achieved without much enduring.");
sendto(socketAddress, myPacket, sizeof(Packet_struct);
myPacket.dataType = STRUCT_IN_THE_PACKET;
memcpy(myPacket.data, (void*)&myStruct, sizeof(myStruct);
sendto(socketAddress, myPacket, sizeof(Packet_struct);
S:好的。
ZMM:现在,我们就跟着接收函数走吧。它必须查询发送的数据类型并将数据复制到声明为该类型的变量中。原谅我,但我忘记了recvfrom()
函数的确切含义。
char[300] receivedString;
struct myStruct receivedStruct;
recvfrom(socketDescriptor, myPacket, sizeof(myPacket);
switch(myPacket.dataType)
{
case STRING_IN_THE_PACKET:
// note the cast of the void* data into type "character pointer"
&receivedString[0] = (char*)&myPacket.data;
printf("The string in the packet was \"%s\".\n", receivedString);
break;
case STRUCT_IN_THE_PACKET:
// note the case of the void* into type "pointer to myStruct"
memcpy(receivedStruct, (struct myStruct *)&myPacket.data, sizeof(receivedStruct));
break;
}
ZMM:你开悟了吗?首先,向编译器询问要提交的数据的大小(也就是字节数)sendto()
。您发送的原始数据的类型也随之发送。然后接收者查询原始数据的类型,并使用它来调用从“指向 void 的指针”(通用指针)到原始数据的类型(int、char[]、结构、 ETC。)
S:嗯,我试试看。
ZMM:安静地走。