我正在尝试编写一些合理的通用网络代码。我有几种数据包,每个数据包由不同的结构表示。发送所有内容的功能如下:
- (void)sendUpdatePacket:(MyPacketType)packet{
for(NSNetService *service in _services)
for(NSData *address in [service addresses])
sendto(_socket, &packet, sizeof(packet), 0, [address bytes], [address length]);
}
我真的希望能够发送此函数任何类型的数据包,而不仅仅是MyPacketType数据包。
我想也许如果函数def是:
- (void)sendUpdatePacket:(void*)packetRef
我可以将任何指针传递给数据包。但是,在不知道数据包类型的情况下,我无法取消引用指针。
如何编写一个函数来接受任何类型的原语/结构作为其参数?
答案 0 :(得分:4)
您要实现的目标是polymorphism,这是一个OO概念。
因此,虽然在C ++(或其他OO语言)中实现这一点非常容易,但在C语言中更具挑战性。
你可以解决的一种方法是创建一个通用的“数据包”结构,例如:
typedef struct {
void* messageHandler;
int messageLength;
int* messageData;
} packet;
messageHandler
成员是指向可以处理消息类型的回调例程的函数指针,而messageLength
和messageData
成员是相当不言自明的。
我们的想法是,您传递packetStruct
的方法将使用Tell, Don't Ask原则来调用messageHandler
指向的特定消息处理程序指针,并传入messageLength
}和messageData
没有解释它。
调度函数(由messageHandler
指向)将是特定于消息的,并且能够将messageData
强制转换为适当的有意义类型,然后可以从中提取有意义的字段,加工等
当然,在使用继承,虚拟方法等的C ++中,这一切都变得更加容易和优雅。
修改强>
回应评论:
我有点不清楚如何能够施展 messageData到适当的 有意义的类型,然后 可以提取有意义的字段 从它和处理等“将是 来完成的。
您将为特定消息类型实现处理程序,并将messageHandler
成员设置为此处理程序的函数指针。例如:
void messageAlphaHandler(int messageLength, int* messageData)
{
MessageAlpha* myMessage = (MessageAlpha*)messageData;
// Can now use MessageAlpha members...
int messageField = myMessage->field1;
// etc...
}
您可以通过这种方式定义messageAlphaHandler()
,以允许任何类轻松获取指向它的函数指针。您可以在启动应用程序时执行此操作,以便从头开始注册消息处理程序。
请注意,要使此系统正常工作,所有消息处理程序都需要共享相同的函数签名(即返回类型和参数)。
或者就此而言,messageData如何 将首先创建 从我的结构。
你是如何得到分组数据的?你是手动创建它,从套接字读取它?无论哪种方式,您需要将其编码为某个字节串。 int*
成员(messageData
)仅仅是指向编码数据开始的指针。 messageLength
成员是此编码数据的长度。
在您的消息处理程序回调中,您可能不希望继续将数据作为原始二进制/十六进制数据进行操作,而是根据以下方式解释有意义的信息。消息类型。
将其转换为结构本质上将原始二进制信息映射到与您正在处理的消息的协议匹配的一组有意义的属性。
答案 1 :(得分:3)
关键是你必须意识到计算机中的所有东西都只是一个字节数组(或单词或双字)。
ZEN MASTER MUSTARD坐在他的办公桌旁,盯着他的显示器,盯着看似随机的复杂图案。学生接近。
学生:师父?我可以打断吗?
Zen Master Mustard:我的儿子已经回答了你自己的询问。
S:什么?
ZMM:通过询问关于打断我的问题,你打扰了我。S:哦,对不起。我有一个关于从不同地方移动不同大小的结构的问题。
ZMM:如果这是真的,那么你应该咨询一位擅长这类事情的大师。我建议你去看看DotPuft大师,他对移动大型金属结构(如跟踪雷达)的知识非常了解。 Master DotPuft还可以使羽毛重量应变计中最轻微的元素随着鸽子的呼吸力而移动。向右转,然后在到达hi-bay的门时向左转。那里住着Master DotPuft。
S:不,我的意思是在计算机的存储器中将不同大小的大型结构从一个地方移动到另一个地方。
ZMM:如果你愿意的话,我可以帮助你做出这样的努力。描述你的问题。S:具体来说,我有一个c函数,我想接受几种不同类型的结构(它们将代表不同类型的数据包)。所以我的struct数据包将作为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:你有没有向Zen Master MANTAR描述你的问题! ?
S:是的,他说,“这只是一个指针.C中的所有内容都是一个指针。”当我让他解释时,他说,“Bok,bok,让我离开我的办公室。”
ZMM:你真的和主人说过话。这对你没有帮助吗?S:呃,呃,不。然后我问了Zen Master 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:一种方法是枚举可能发送的不同类型的数据,然后将数据类型与数据一起发送。 Zen Masters将此称为“元数据”,即“有关数据的数据”。您的接收函数必须检查元数据以确定要发送的数据类型(struct,float,字符数组),然后使用此信息将数据转换回其原始类型。首先,考虑传输功能:
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()
的数据大小(a.k.a.字节数)。您发送原始数据的类型也是一样的。接收器然后查询原始数据的类型,并使用它从“指向void的指针”(通用指针)调用正确的转换,转换为原始数据的类型(int,char [],一个结构,等)
是的,嗯,我会试一试。
ZMM:安静地走吧。