Pregunta

Estoy intentando escribir un código de red razonablemente genérico.Tengo varios tipos de paquetes, cada uno representado por una estructura diferente.La función donde ocurren todos mis envíos se ve así:

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

Realmente me gustaría poder enviar esta función CUALQUIER tipo de paquete, no solo paquetes MyPacketType.

Pensé que tal vez si la función def fuera:

- (void)sendUpdatePacket:(void*)packetRef

Podría pasar cualquier tipo de puntero al paquete.Pero, sin saber el tipo de paquete, no puedo desreferenciar el puntero.

¿Cómo escribo una función para aceptar cualquier tipo de primitiva/estructura como argumento?

¿Fue útil?

Solución 2

La clave es que debes darte cuenta de que todo en una computadora es solo una matriz de bytes (o palabras o palabras dobles).

ZEN MASTER MUSTARD está sentado en su escritorio mirando su monitor y observando un patrón complejo de caracteres aparentemente aleatorios.Se acerca un ESTUDIANTE.

Alumno:¿Maestro?¿Puedo interrumpir?

Mostaza Maestro Zen:Has respondido a tu propia pregunta, hijo mío.

S:¿Qué?

ZMM:Al hacer tu pregunta sobre interrumpirme, me has interrumpido.

S:Oh, lo siento.Tengo una pregunta sobre cómo mover estructuras de diferentes tamaños de un lugar a otro.

ZMM:Si eso es cierto, entonces deberías consultar a un maestro que se destaque en esas cosas.Le sugiero que visite al Maestro DotPuft, quien tiene un gran conocimiento en mover grandes estructuras metálicas, como radares de seguimiento, de un lugar a otro.Master DotPuft también puede hacer que los elementos más pequeños de una galga extensométrica de peso pluma se muevan con la fuerza del aliento de una paloma.Gire a la derecha y luego a la izquierda cuando llegue a la puerta del hi-bay.Allí habita el Maestro DotPuft.

S:No, me refiero a mover grandes estructuras de diferentes tamaños de un lugar a otro en la memoria de una computadora.

ZMM:Puedo ayudarle en ese esfuerzo, si lo desea.Describe tu problema.

S:Específicamente, tengo una función c que quiero aceptar varios tipos diferentes de estructuras (representarán diferentes tipos de paquetes).Entonces mis paquetes de estructura se pasarán a mi función como void*.Pero sin saber el tipo, no puedo elegirlos ni hacer mucho de nada.Sé que este es un problema que tiene solución, porque sento() de socket.h hace exactamente eso:

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

donde sendto se llamaría así:

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

ZMM:¿Le describiste tu problema al Maestro Zen MANTAR!?

S:Sí, dijo: "Es sólo un indicador.Todo en C es un puntero". Cuando le pedí que me explicara, dijo: "Bok, bok, lárgate de mi oficina".

ZMM:En verdad, has hablado con el maestro.¿Esto no te ayudó?

S:Mmmm, no.Entonces le pregunté al Maestro Zen Max.

ZMM:Sabio es él.¿Qué te resultó útil su consejo?

S:No.Cuando le pregunté sobre sendto(), simplemente agitó los puños en el aire.Es sólo una serie de bytes."

ZMM:De hecho, Zen Master Max tiene tau.

S:Sí, tiene tau, pero ¿cómo trato con los argumentos de función de tipo void*?

ZMM:Para aprender, primero hay que desaprender.La clave es que debes darte cuenta de que todo en una computadora es solo una matriz de bytes (o palabras o palabras dobles).Una vez que tenga un puntero al comienzo de un búfer y la longitud del búfer, puede enviarlo a cualquier lugar sin necesidad de saber el tipo de datos colocados en el búfer.

S:DE ACUERDO.

ZMM:Considere una cadena de texto legible por humanos."¿Planeas una torre que atravesará las nubes?Pongan primero los cimientos de la humildad." Tiene 82 bytes de longitud.O, tal vez, 164 si se utiliza el malvado Unicode.¡Protégete de las mentiras de Unicode!Puedo enviar este texto a sendto() proporcionando un puntero al comienzo del búfer que contiene la cadena y la longitud del búfer, así:

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:Tenga en cuenta que el compilador calcula automáticamente el número de bytes del búfer de caracteres.El número de bytes ocupados por cualquier tipo de variable es de un tipo llamado "size_t".Probablemente sea equivalente al tipo "long" o "unsinged int", pero depende del compilador.

S:Bueno, ¿qué pasa si quiero enviar una estructura?

ZMM:Entonces, enviemos una estructura.

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:Sí, eso es excelente para transmitir cosas a través de sockets TCP/IP.Pero ¿qué pasa con la mala función de recepción?¿Cómo puedo saber si estoy enviando una matriz de caracteres o una estructura?

ZMM:Una forma es enumerar los diferentes tipos de datos que se pueden enviar y luego enviar el tipo de datos junto con los datos.Los Maestros Zen se refieren a esto como "metadatos", es decir, "datos sobre los datos".Su función de recepción debe examinar los metadatos para determinar qué tipo de datos (estructura, flotante, matriz de caracteres) se envían y luego usar esta información para devolver los datos a su tipo original.Primero, considere la función de transmisión:

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:Está bien.

ZMM:Ahora, simplemente caminamos junto con la función de recepción.Debe consultar el tipo de datos que se enviaron y copiar los datos en una variable declarada de ese tipo.Perdóname, pero se me olvida la forma exacta del recvfrom() función.

   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:¿Has alcanzado la iluminación?Primero, se le pregunta al compilador el tamaño de los datos (también conocido comoel número de bytes) que se enviará a sendto().También envía el tipo de datos originales.Luego, el receptor consulta el tipo de datos originales y lo usa para llamar a la conversión correcta desde "puntero a vacío" (un puntero genérico) hasta el tipo de datos originales (int, char[], a struct, etc.)

S:Bueno, lo intentaré.

ZMM:Ve en paz.

Otros consejos

Lo que estamos tratando de lograr es polimorfismo, que es un concepto orientado a objetos.

Así, mientras que esto sería bastante fácil de implementar en C ++ (u otros lenguajes orientados a objetos), que es un poco más difícil en C.

Una forma en que podría moverse es para crear una estructura genérica "paquetes" como este:

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

Cuando el miembro de messageHandler es un puntero de función a una rutina de devolución de llamada que puede procesar el tipo de mensaje, y los miembros messageLength y messageData son bastante auto-explicativo.

La idea es que el método que se pasa el packetStruct que usaría el Tell, Don' t Pregunte principio para invocar el puntero del controlador de mensajes específicos a por messageHandler, pasando el messageLength y messageData sin interpretarlo.

La función de despacho (apuntado por messageHandler) sería-mensaje específico y será capaz de emitir el messageData al tipo significativa apropiado, y luego los campos significativos se puede extraer de ella y se procesa, etc.

Por supuesto, todo esto es mucho más fácil y más elegante en C ++ con la herencia, métodos virtuales y similares.


Editar

En respuesta al comentario:

  

Estoy un poco claro cómo "capaz de lanzar   la messageData a la apropiada   tipo significativo, y luego el   campos significativos pueden ser extraídos   de ella y procesado, etc." sería   logrado.

Se podría implementar un controlador para un tipo de mensaje específico, y establecer el miembro messageHandler ser un puntero de función a este controlador. Por ejemplo:

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

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

Se podría definir messageAlphaHandler() de tal manera para permitir que cualquier clase para obtener un puntero de función a ella fácilmente. Usted puede hacer esto en el inicio de la aplicación para que los controladores de mensajes se registran desde el principio.

Tenga en cuenta que para que este sistema funcione, todos los controladores de mensajes tendrían que compartir la firma misma función (es decir, el tipo de retorno y parámetros).

  

O para el caso, ¿cómo messageData   se crearía en el primer lugar   de mi estructura.

¿Cómo le va usted paquete de datos? ¿Va a crear de forma manual, leerlo fuera de una toma de corriente? De cualquier manera, es necesario para codificar en algún lugar como una cadena de bytes. El miembro int* (messageData) es simplemente un puntero al comienzo de los datos codificados. El miembro messageLength es la longitud de estos datos codificado.

En su mensaje de devolución de llamada manejador, que no quiere probablemente no quiera seguir para manipular los datos como datos binarios sin formato / hexagonales, pero en lugar de Interpretar la información de una manera significativa según el tipo de mensaje.

casting a una estructura esencialmente los mapas de la información binaria cruda a un conjunto significativo de atributos a juego con el protocolo del mensaje que está procesando.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top