Question

Je suis en train d'écrire un code de réseau raisonnablement générique. J'ai plusieurs types de paquets, chacun représenté par un struct différent. La fonction où tout mon envoi se produit ressemble à:

- (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]); 
}

Je voudrais vraiment pouvoir envoyer cette fonction tout type de paquet, et pas seulement des paquets MyPacketType.

Je pensais que peut-être si la définition de la fonction est:

- (void)sendUpdatePacket:(void*)packetRef

Je pourrais passer anykind de pointeur paquet. Mais, sans connaître le type de paquet, je ne peux pas déréférencer le pointeur.

Comment puis-je écrire une fonction d'accepter toute sorte de / struct primitive comme argument?

Était-ce utile?

La solution 2

La clé est que vous devez comprendre que tout dans un ordinateur est juste un tableau d'octets (ou, mots ou doubles mots).

ZEN MASTER MOUTARDE est assis à son bureau à regarder son écran à regarder un schéma complexe de caractères apparemment aléatoires. ÉTUDIANT approche.

Étudiant: Master? Puis-je vous interrompre?

Maître Zen moutarde. Vous avez répondu à votre demande, mon fils

S: Qu'est-ce que

ZMM. En posant votre question sur moi de vous interrompre, vous me avez interrompu

S: Oh, désolé. J'ai une question sur le déplacement des structures de taille variable d'un endroit à.

MMZ: Si cela est vrai, alors vous devriez consulter un maître qui excelle à de telles choses. Je suggère, vous payez une visite au Maître DotPuft, qui a une grande connaissance dans le déplacement de grandes structures métalliques, tels que les radars de suivi, d'un endroit à l'autre. Maître DotPuft peut également provoquer les moindres éléments d'une jauge de contrainte poids plume à se déplacer avec la force du souffle de la colombe. Tournez à droite, puis tournez à gauche lorsque vous atteignez la porte du salut-bay. Il habite Maître DotPuft.

S: Non, je veux dire le déplacement de grandes structures de différentes tailles d'un endroit à la mémoire d'un ordinateur

.

MMZ: Je peux vous aider dans cette entreprise, si vous le souhaitez. Décrivez votre problème.

S: Plus précisément, j'ai une fonction c que je veux accepter plusieurs types de struct différents (ils représenteront différents types de paquets). Donc, mes paquets de struct seront transmis à ma fonction void *. Mais sans connaître le type, je ne peux pas les jeter, ou vraiment faire quoi que ce soit. Je sais que c'est un problème résoluble, parce que Sento () de socket.h fait exactement cela:

    ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr,socklen_t dest_len);

où sendto serait appelé comme:

    sendto(socketAddress, &myPacket, sizeof(myPacket), Other args....);

MMZ: Avez-vous décrire votre problème à Maître Zen MANTAR! ?

S: Oui, il a dit: "Il est juste un pointeur Tout en C est un pointeur.". Quand je lui ai demandé d'expliquer, dit-il, « Bok, bok, obtenir l'enfer hors de mon bureau. »

MMZ: En vérité, vous avez parlé au maître. Est-ce que cela ne vous aidera pas?

S: Um, euh, non. Alors j'ai demandé Maître Zen Max.

MMZ: Wise est-il. Quel était son conseil pour vous utile?

S: Non, quand je lui ai demandé sendto (), il a juste tourbillonné ses poings dans l'air. Il est juste un tableau d'octets. "

MMZ:. En effet, le Maître Zen Max a tau

S: Oui, il a tau, mais comment puis-je traiter avec des arguments de fonction de type void *

?

MMZ: Pour en savoir plus, vous devez d'abord désapprendre. La clé est que vous devez comprendre que tout dans un ordinateur est juste un tableau d'octets (ou, mots ou doubles mots). Une fois que vous avez un pointeur au début d'un tampon, et la longueur de la mémoire tampon, vous pouvez l'envoyer partout sans besoin de connaître le type de données placées dans la mémoire tampon.

S:. OK

MMZ: Considérons une chaîne de texte lisible par l'homme. « Vous planifiez une tour qui percer les nuages? Poser d'abord le fondement de l'humilité. » Il est de 82 octets. Ou, peut-être, 164 si le mal Unicode est utilisé. Garde-toi contre les mensonges de Unicode! Je peux soumettre ce texte à sendto () en fournissant un pointeur au début du tampon qui contient la chaîne, et la longueur du tampon, comme suit:

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));

MMZ: Notez bien que le nombre d'octets de la mémoire tampon de caractères est calculé automatiquement par le compilateur. Le nombre d'octets occupés par tout type variable est d'un type appelé « size_t ». Il est probable équivalent au type « long » ou « unsinged int », mais il dépend compilateur.

S: Eh bien, si je veux envoyer un struct

ZMM. Envoyons un struct, puis

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: Oui, c'est super à transmettre des choses sur des sockets TCP / IP. Mais qu'en est-la fonction de réception pauvres? Comment peut-il dire si je vous envoie un tableau de caractères ou d'un struct?

MMZ: La première consiste à énumérer les différents types de données qui peuvent être envoyées, und ensuite envoyer le type de données ainsi que les données. Maîtres Zen se réfèrent à cette « métadonnées », c'est-à-dire « données sur les données ». Votre fonction de réception doit examiner les métadonnées afin de déterminer quel type de données (struct, flottant, un tableau de caractères) est envoyé, puis utiliser ces informations pour jeter les données dans son type d'origine. Considérons d'abord la fonction de transmission:

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: Très bien.

MMZ: Maintenant, il suffit de nous longeons avec la fonction de réception. Il doit interroger le type de données envoyées et la copie des données dans une variable déclarée de ce type. Pardonnez-moi, mais je ne me souviens exactement de la fonction 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;
    }

MMZ: Avez-vous atteint l'illumination? Tout d'abord, on demande au compilateur de la taille des données (le nombre d'alias octets) à soumettre à sendto(). Vous envoyez le type de données d'origine est envoyé en même temps que bien. Le récepteur puis les requêtes pour le type de données d'origine, et l'utilise pour appeler le bon casting de « pointeur sur void » (un pointeur générique), sur le type de données d'origine (int, char [], un struct, etc.)

S: Eh bien, je vais faire un essai

.

ZMM. Allez dans la paix

Autres conseils

Ce que vous essayez d'atteindre est polymorphisme , qui est un concept OO.

Ainsi, alors que ce serait assez facile à mettre en œuvre en C ++ (ou d'autres langages OO), il est un peu plus difficile en C.

Une façon que vous pourriez obtenir est autour de créer une structure « paquet » générique tel que ceci:

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

Lorsque l'élément de messageHandler est un pointeur de fonction sur une routine de rappel qui peut traiter le type de message, et les éléments de messageLength et messageData sont assez explicites.

L'idée est que la méthode que vous passez le packetStruct à utiliserait Tell, Don » Demandez t principe d'invoquer le pointeur de gestionnaire de message spécifique par messageHandler, en passant dans le messageLength et messageData sans l'interpréter.

La fonction de répartition (pointée par messageHandler) serait un message spécifique et sera en mesure de lancer le messageData au type de sens approprié, puis les champs significatifs peut être extrait de celui-ci et traitées, etc.

Bien sûr, tout cela est beaucoup plus facile et plus élégant en C ++ avec l'héritage, les méthodes virtuelles et autres.


Modifier

En réponse au commentaire:

  

Je suis un peu mal comment « capable de lancer   le MessageData au approprié   le type significatif, puis la   champs significatifs peuvent être extraits   de celui-ci et traitées, etc. » serait   accompli.

Vous implémenter un gestionnaire pour un type de message spécifique et définissez l'élément messageHandler être un pointeur de fonction à ce gestionnaire. Par exemple:

void messageAlphaHandler(int messageLength, int* messageData)
{
    MessageAlpha* myMessage = (MessageAlpha*)messageData;

    // Can now use MessageAlpha members...
    int messageField = myMessage->field1;
    // etc...
}

Vous définiriez messageAlphaHandler() de telle manière à permettre à une classe pour obtenir un pointeur de fonction facilement. Vous pouvez le faire au démarrage de l'application afin que les gestionnaires de messages sont enregistrés depuis le début.

Notez que pour que ce système fonctionne, tous les gestionnaires de messages devront partager la même signature de fonction (à savoir le type de retour et les paramètres).

  

Ou pour cette question, comment MessageData   serait créé en premier lieu   de mon struct.

Comment êtes-vous vous paquet de données? Créez-vous manuellement, la lecture hors tension une prise? De toute façon, vous devez encoder quelque part comme une chaîne d'octets. L'élément de int* (de messageData) est simplement un pointeur vers le début des données codées. L'élément de messageLength est la longueur de ces données codées.

Dans votre message de rappel de gestionnaire, vous ne ne voulez pas probablement pas envie de continuer à manipuler les données sous forme de données binaires brutes / hex, mais au lieu de interprétez les informations de façon significative selon le type de message.

Castings à un struct essentiellement cartes de l'information binaire brute sur un ensemble significatif d'attributs correspondant au protocole du message que vous traitez.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top