0

我正在尝试编写一些相当通用的网络代码。我有几种数据包,每种数据包由不同的结构表示。我所有发送发生的函数如下所示:

- (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

我可以传入任何类型的数据包指针。但是,在不知道数据包类型的情况下,我无法取消引用指针。

如何编写一个函数来接受任何类型的原始/结构作为其参数?

4

2 回答 2

4

您要实现的是polymorphism,这是一个 OO 概念。

因此,虽然这在 C++(或其他 OO 语言)中很容易实现,但在 C 中更具挑战性。

您可以解决的一种方法是创建一个通用的“数据包”结构,例如:

typedef struct {
    void* messageHandler;
    int   messageLength;
    int*  messageData;
} packet;

其中messageHandler成员是指向可以处理消息类型的回调例程的函数指针,并且messageLengthmessageData成员是不言自明的。

这个想法是,您传递packetStruct给的方法将使用Tell,Don't Ask原则来调用特定的消息处理程序指针 by messageHandler,传入messageLengthandmessageData而不解释它。

调度函数(由 指向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成员是这个编码数据的长度。

在您的消息处理程序回调中,您可能不想继续将数据作为原始二进制/十六进制数据进行操作,而是根据消息类型以有意义的方式解释信息。

将其转换为结构本质上将原始二进制信息映射到一组有意义的属性,这些属性与您正在处理的消息的协议相匹配。

于 2009-11-19T04:17:05.517 回答
3

关键是您必须意识到计算机中的所有内容都只是一个字节数组(或字或双字)。

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:安静地走。

于 2009-11-25T01:41:21.470 回答