Envoltura de mariscalización de datos para un servidor TCP -- # campo con performance campo con serialization campo con wrapper camp codereview Relacionados El problema

Data marshaling wrapper for a TCP server


12
vote

problema

Español

Después de una gran cantidad de investigaciones, creé este envoltorio que creo que encaja muy bien con mis necesidades. Mi proyecto es un servidor TCP y usaré esta envoltura cada vez que el servidor reciba un paquete. Básicamente, la envoltura trabajará marhaling crudo byte[] en la representación de nivel superior de los datos como estructuras maravillosas.

¿Qué piensas de esta envoltura? ¿Algún sugerencia sobre cómo puedo mejorarlo? Estoy muy preocupado por el rendimiento.

  public static class MyMarshal {     /// <summary>     /// Marshals a raw buffer to a given marshalable struct.     /// </summary>     public static unsafe T GetStructure<T>(byte[] buffer) where T : struct     {         fixed (byte* bufferPin = buffer)         {             return (T)Marshal.PtrToStructure(new IntPtr(bufferPin), typeof(T));         }     }      /// <summary>     /// Marshals a given T instance into a raw buffer.     /// </summary>     public static unsafe byte[] GetBytes<T>(T obj) where T : struct     {         byte[] rawBuffer = new byte[Marshal.SizeOf(obj)];          fixed (byte* rawBufferPin = rawBuffer)         {             Marshal.StructureToPtr<T>(obj, new IntPtr(rawBufferPin), false);         }          return rawBuffer;     }      /// <summary>     /// Crates a zero-initialized marshaled instance of T     /// </summary>     public static unsafe T CreateEmpty<T>() where T : struct     {         int typeSize = Marshal.SizeOf(typeof(T));          byte* rawBuffer = stackalloc byte[typeSize];          for (int i = 0; i < typeSize; i++)             rawBuffer[i] = 0;          T zeroInited = (T)Marshal.PtrToStructure(new IntPtr(rawBuffer), typeof(T));          return zeroInited;     } }   

Summing Up, la envoltura tiene 3 métodos:

  • GetStructure (usado justo después de recibir cualquier paquete válido para obtener la estructura respectiva)
  • GetBytes3 (se usa cuando quiero enviar un paquete a un cliente, convierte un valor de estructura dado a un búfer sin procesar para enviar al cliente)
  • CreateEmpty (se usa para crear una instancia inicializada cero de una estructura dada. Este método se usa cuando tengo estructuras complejas con matrices no blastables, etc. El objetivo de este método es tener Un método genérico de inicializador cero incluso a estructuras no blastables, aquellas con matrices y / o cuerdas no blastables).

También quiero levantar otras preguntas:

  1. ¿Qué piensa sobre este uso mixto del código inseguro y el Marshal ? ¿Hay algún problema para usar de esa manera?
  2. En el método MyMarshal.CreateEmpty , ¿qué piensa sobre el stackalloc ? Algunas estructuras que se inicializarán con este método serán un poco grandes (generalmente algo entre 100 y 1024 bytes). ¿Hay algún inconveniente o peligro de asignar esta longitud en la pila varias veces por segundos? Estoy esperando procesar algo alrededor de 5K Paquete por segundo.

Además, el servidor TCP funcionará de manera asíncrona utilizando el patrón ASYNC / AWAIT, por lo que no espero lidiar con los problemas especificados compartidos y otros problemas comunes a una aplicación multi-roscada.

Original en ingles

After a lot of research, I created this wrapper which I think fits very well to my needs. My project is a TCP server and I will be using this wrapper every time the server receives a packet. Basically, the wrapper will work marshaling raw byte[] into higher-level representation of the data as marshalable structs.

What do you think about this wrapper? Any hint on how I can improve it? I'm very concerned about performance.

public static class MyMarshal {     /// <summary>     /// Marshals a raw buffer to a given marshalable struct.     /// </summary>     public static unsafe T GetStructure<T>(byte[] buffer) where T : struct     {         fixed (byte* bufferPin = buffer)         {             return (T)Marshal.PtrToStructure(new IntPtr(bufferPin), typeof(T));         }     }      /// <summary>     /// Marshals a given T instance into a raw buffer.     /// </summary>     public static unsafe byte[] GetBytes<T>(T obj) where T : struct     {         byte[] rawBuffer = new byte[Marshal.SizeOf(obj)];          fixed (byte* rawBufferPin = rawBuffer)         {             Marshal.StructureToPtr<T>(obj, new IntPtr(rawBufferPin), false);         }          return rawBuffer;     }      /// <summary>     /// Crates a zero-initialized marshaled instance of T     /// </summary>     public static unsafe T CreateEmpty<T>() where T : struct     {         int typeSize = Marshal.SizeOf(typeof(T));          byte* rawBuffer = stackalloc byte[typeSize];          for (int i = 0; i < typeSize; i++)             rawBuffer[i] = 0;          T zeroInited = (T)Marshal.PtrToStructure(new IntPtr(rawBuffer), typeof(T));          return zeroInited;     } } 

Summing up, the wrapper have 3 methods:

  • GetStructure (used right after I receive any valid packet to get the respective structure)
  • GetBytes (used when I want to send a packet to a client, converts a given struct value to a raw buffer to be send to the client)
  • CreateEmpty (used to create a zero-initialized instance of a given struct. This method is used when I have complex structures with non-blittable arrays, etc. The goal of this method is to have a generic zero-initializer method even to non-blittable structs - those with non-blittable arrays and/or strings).

I want to raise another questions too:

  1. What do you think about this mixed use of unsafe code and the Marshal class? Is there any problem on using that way?
  2. In the method MyMarshal.CreateEmpty, what do you think about the stackalloc usage? Some structures that will be initialized with this method will be a little big (usually something between 100 and 1024 bytes). Is there any downside or danger of allocating this length in the stack several times per seconds? I'm expecting to process something around 5k packet per second.

Also, he TCP server will work asynchronously using the async/await pattern, so I don't expect to deal with shared stated and other common problems to a multi-threaded application.

           
       
       

Lista de respuestas

2
 
vote

El envoltorio parece bastante bueno conmigo. El único cambio que podría considerar es que 9988776655544330 realmente solo necesita correr una vez para cada tipo; Dado que estas son estructuras, puede devolver copias del mismo objeto vacío cada vez. Algo así debería funcionar:

  static class EmptyHolder<T> where T : struct {     public static T EmptyInstance;     // assign EmptyInstance in static constructor }   

y luego:

  public static unsafe T CreateEmpty<T>() where T : struct {     return EmptyHolder<T>.EmptyInstance; }   

Por supuesto, hay una compensación de una memoria incollectable para cada tipo con el que llama a esto. Y con cualquier cosa relacionada con el rendimiento, deberá medir para ver si ofrece algún beneficio. El código original debe ser bastante rápido, así que tal vez no sea significativo.

No debería haber ningún peligro en ninguna importancia con la frecuencia con la que llame a Stackalloc, por definición, no es posible que dos llamadas de este tipo afecten la misma pila al mismo tiempo. Y no hay peligro de ningún tipo de recursión que tenga más de 1 de estas asignaciones en la misma pila, ya que el bloque se libera al final de esta función.

 

The wrapper seems pretty good to me. The only change you might consider is that CreateWrapper really only needs to run once for each type; since these are structs it can return copies of the same empty object each time. Something like this should work:

static class EmptyHolder<T> where T : struct {     public static T EmptyInstance;     // assign EmptyInstance in static constructor } 

and then:

public static unsafe T CreateEmpty<T>() where T : struct {     return EmptyHolder<T>.EmptyInstance; } 

Of course there's a tradeoff of an uncollectable memory for each type you call this with. And with anything performance-related, you'll need to measure to see if it delivers any benefit. The original code should be pretty fast so maybe it's not significant.

There shouldn't be any danger in no matter how often you call stackalloc, by definition it's not possible for any two such calls to affect the same stack at the same time. And there's no danger of any sort of recursion that would have more than 1 of these allocations on the same stack since the block is freed at the end of this function.

 
 
2
 
vote

Simplemente no hay suficientes comentarios en su código para que el desarrollador de C # promedio de GROK sea lo que está sucediendo rápidamente. No quito un gran grupo de personas, sino que enfrentemos los hechos aquí, vivimos nuestras vidas en el maravilloso mundo donde se administra memoria para nosotros, y los punteros nunca se cree, excepto en las "clases abstractas son tipos de referencia. "Tipo de camino. Sé amable con el JR. Dev que tiene que leer este código a partir de ahora. Deje algunos comentarios breves sobre lo que está haciendo este código y por qué ha elegido usar el 9988777665544333 palabra clave y los punteros crudos.

Aparte de eso, parece ser una implementación sana para mí. Por lo que vale la pena, se parece mucho a una implementación propia. Personalmente, elegí usar Marshal para todo, para que no tuviera que compilar con la bandera insegura, o declarar los usos de los métodos como unsafe . No puedo decir si se desempeñara mejor o peor que su implementación en términos de memoria o velocidad. Si una implementación segura fue tan buena, preferiría que el código no gotee los detalles de la memoria no administrados a través de la abstracción.


Publicé Mi propia implementación para su revisión . No requiere el uso del unsafe palabra clave y contexto.

 

There simply aren't enough comments in your code for the average C# developer to grok what's happening quickly. I don't meant to slight a large group of people, but let's face facts here, we live our lives in the wonderful world where memory is managed for us, and pointers are never thought of, excepts in the abstract "classes are reference types" kind of way. Be nice to the Jr. dev that has to read this code 5 years from now. Leave some brief comments about what this code is doing and why you've chosen to use the fixed keyword and raw pointers.

Other than that, it appears to be a sane implementation to me. For what it's worth, it looks very much like an implementation of my own. Personally, I chose to use Marshal for everything so that I didn't I have to compile with the unsafe flag, or declare uses of the methods as unsafe. I can't say if it would perform any better or worse than your implementation in terms of memory or speed. If a safe implementation was just as good, I would prefer it so that the code doesn't leak the the unmanaged memory details up through the abstraction.


I posted my own implementation for review. It does not require the use of the unsafe keyword and context.

 
 

Relacionados problema

7  Clase de Wrapper Universal OpenGL Object RAII  ( Universal opengl object raii wrapper class ) 
Creé una clase de envoltura RAII de OpenGL de Universal, que solo se ocupa de la creación de objetos y la destrucción. Aquí está mi código y razonamiento detr...

2  Lanzamiento de excepciones en una envoltura de base de datos  ( Throwing exceptions in a database wrapper ) 
Espero que los métodos de extensión ThrowArgumentNullExceptionIfNullOrEmpty y ThrowNullReferenceExceptionIfNullOrEmpty son directas. Tengo una propiedad...

8  Envoltura Libusb Library en C ++  ( Wrapping libusb library in c ) 
Quiero usar la biblioteca de Libusb en mi aplicación C ++. He creado clases que envuelven las funciones de Libusb. Puede ver que la API de Libusb se divide en...

1  TweetResolver Class, que se utilizará en un proyecto GraphQL  ( Tweetresolver class to be used in a graphql project ) 
Este es mi módulo TweetResolver (Tweet-resolver.js): import Tweet from '../../models/Tweet'; import { requireAuth } from '../../services/auth'; export d...

2  Punteros para el formato de encabezado, diseño y diseño de la biblioteca de la biblioteca C ++ / CLI  ( Pointers for c cli library header formatting layout and design ) 
He diseñado una biblioteca de Wrapper C ++ / CLI que permite aplicaciones C ++ en Windows, y otras plataformas que pueden cargar C DLLs (por ejemplo, Java a t...

8  Envoltura alrededor de POPLIB.POP3 y POPLIB.POP3_SSL  ( Wrapper around pythons poplib pop3 and poplib pop3 ssl ) 
De vez en cuando uso el módulo POPLIB de Python para inspeccionar / manipular los buzones POP3, a menudo interactivamente. Sin embargo, encuentro la interfaz ...

5  Una envoltura de iterador que en la deferencia, devuelve el valor de un miembro (función)  ( An iterator wrapper that on dereferencing returns the value of a member functi ) 
Escribí una envoltura de iterador simple que se puede utilizar en e.g. std::find y <body> <div id='container'> <div id='header'>Have a chat</div> ...

3  Estructura de la envoltura API  ( Structure of api wrapper ) 
Estoy construyendo una envoltura API para una API de jabón de contabilidad. Tengo algunas preguntas con respecto a la práctica Bast para la estructura de la...

4  Clase de envoltura de creación de ventanas  ( Window creation wrapper class ) 
Esta pregunta ahora tiene un seguimiento aquí No creo que haya ninguna técnica nueva en mi código, pero en mi opinión está bien, todo en todos (con un poc...

2  API DB simple para insertar un registro  ( Simple db api for inserting a record ) 
Estoy buscando realizar algunas mejoras a una API de crudo deliberadamente escrito en PHP, está diseñado para manejar solicitudes simples y hablar con el DB...




© 2022 respuesta.top Reservados todos los derechos. Centro de preguntas y respuestas reservados todos los derechos