Resumen de la experiencia de desarrollo del lado del servidor Linux lenguaje C

  
 

Después de realizar el desarrollo del lado del servidor, debe tener en cuenta algunos algoritmos y problemas de rendimiento. Después de varios años de desarrollo, tiene algo de experiencia en esta área. Ahora escríbalo y compártalo con usted.

Desarrollo principalmente el lenguaje C en Linux, por lo que la última implementación se basa en el sistema operativo Linux y se explica en el lenguaje C. Otras plataformas e idiomas deben considerarse de manera similar, pero puede haber algunas diferencias en los detalles de la implementación. Intento reducir estas diferencias. Tenga en cuenta que todo el contenido explicado se basa en el desarrollo de sistemas de 32 bits.

El núcleo del desarrollo del programa del servidor es estable, y la eficiencia debe considerarse bajo la premisa de estabilidad. Los principales módulos públicos son el grupo de memoria y el grupo de hilos. Debido a que los programas del servidor generalmente se ejecutan durante mucho tiempo, y con frecuencia crean y liberan operaciones de memoria, entonces, si utiliza los métodos libres y de malloc del sistema, causará mucha fragmentación de la memoria en el sistema, lo que afecta la eficiencia y la estabilidad. La idea principal del conjunto de memoria es llamar primero al sistema malloc del sistema para abrir una gran cantidad de memoria y luego administrar esta gran memoria. Cuando se utiliza la memoria en el programa, el conjunto de memoria asigna memoria. Cuando el programa libera la memoria, solo informa al conjunto de memoria. Realmente no libera la memoria. Grupo de subprocesos, el principio es similar: cuando se trata de algunas tareas al mismo tiempo, es necesario utilizar muchos subprocesos, podemos crear muchos subprocesos primero y luego, cada vez que hay una tarea que procesar, luego encontrar un subproceso libre para dejar que se encargue de la tarea, una vez que se completa el tema. Empezar Esto ahorra la sobrecarga de tiempo de crear un hilo cada vez que se crea una tarea.

Conocimiento preparatorio

La siguiente es una descripción de algunas de las técnicas utilizadas en la encapsulación de bibliotecas. Debido a que nos estamos desarrollando en lenguaje C, para equilibrar la estructura y la eficiencia, necesitamos algunas habilidades para encapsular, de modo que el programa sea lo suficientemente modular como para garantizar la eficiencia del lenguaje C.

container_of macro

Primero introduzca esta macro, esta macro es lo que vi en el kernel de Linux, se usa para derivar la estructura completa a través de un puntero a un miembro de una estructura Puntero, tomemos un ejemplo. Por ejemplo, hay una estructura:
1: struct my_struct 2: {3: int a; 4: int b; 5: char c; 6: short d; 7:};

Eche un vistazo al siguiente código: Br> 1: int 2: main () 3: {4: struct my_struct mys; //crea el objeto de estructura 5: struct my_struct * pmys; //declara un puntero a nuestra estructura 6: char * pc = &( Mys.c); //pc apunta al miembro c de la estructura 7: int * pb = &(mys.b); //pb apunta al miembro b de la estructura 8: pmys = container_of (pc, struct my_struct, c); //pmys en realidad apunta a mys structure 9: pmys = container_of (pb, struct my_struct, b); //pmys sigue apuntando a mys structure 10: //Las dos líneas de código anteriores son las mismas que pmys = &mys; 11: .. 12:}

El código anterior muestra que se puede obtener un puntero a un miembro de una estructura (como pc o pb) llamando a container_of para obtener un puntero a toda la estructura. Esta macro toma tres argumentos, el primer argumento es un puntero a un miembro de la estructura, el segundo argumento es el tipo de construcción y el tercer argumento es la declaración del miembro apuntado por el puntero del miembro en el primer argumento. El famoso nombre en. Esta macro se define de la siguiente manera:
1: #define offsetof (TYPE, MEMBER) ((size_t) &((TYPE *) 0) - > MEMBER) 2: 3: #ifdef WIN32 4: //bajo WIN32 ( No se admite la comprobación de seguridad de tipo) 5: # define container_of (ptr, type, member) /6: ((type *) (ptr) -offsetof (type, member)) 7: 8: #else 9: //10 under linux : # define container_of (ptr, type, miembro) ({/11: const typeof (((type *) 0) - > member) * _mptr = (ptr); /12: (type *) ((char *) _mptr-offsetof (type, member))}} 13: 14: #endif /* * /

En la implementación anterior, se define primero una macro offsetof. Esta macro se utiliza para calcular un miembro de la estructura. El desplazamiento de la dirección con respecto a la dirección de inicio de esta estructura. Bajo el WIN32, puede usar la propia macro de desplazamiento del sistema.

La macro container_of realmente usa la dirección de un miembro de la estructura menos el desplazamiento del miembro con respecto al inicio de la estructura, y la dirección de la estructura es el puntero a la estructura. Cuando se implementa bajo linux: const typeof (((type *) 0) - > member) * _mptr = (ptr); esta oración está haciendo verificaciones de seguridad de tipo. Se garantiza que el tipo de puntero al primer parámetro miembro de la macro será un puntero a este tipo de miembro. Typeof es una extensión de gcc que se usa para obtener el tipo de una expresión. No encontré un sustituto para typeof en WIN32, así que no hice una verificación de seguridad de tipo.

Con la macro container_of, ¿qué podemos hacer? Por ejemplo, si escribimos una lista vinculada, el área de datos en la lista vinculada generalmente se trata como un int en el libro de texto. En realidad, puede ser una estructura complicada y, como persona que escribe una lista vinculada, no sé qué datos almacenará el usuario en la lista vinculada. La práctica habitual es que el área de datos sea un vacío * para que el usuario pueda usar este puntero para almacenar cualquier objeto. Si el usuario desea almacenar sus propios datos, puede crear un objeto con una estructura personalizada y luego usar este puntero para apuntar a la estructura. Cada elemento de la lista enlazada ocupa un vacío * además de la memoria ocupada por el área de datos. Cuando se desarrolla en el lado del servidor, la cantidad de datos es grande, lo que desperdicia mucho vacío *. Podemos resolver este problema por los siguientes métodos.

Definición de la estructura de los elementos en la lista enlazada:
1: struct link_item 2: {3: struct link_item * next; 4:};

Los métodos proporcionados para el funcionamiento de la lista enlazada se basan en struct link_item * El método del puntero. El usuario declara su propia estructura de elemento de lista vinculada cuando se utiliza:
1: struct user_link_item 2: {3: struct link_item lk_item; 4: int my_data1; 5: short my_data2; 6: //... 7:}; p> Esta estructura tiene un miembro lk_item de tipo struct link_item. Cuando usamos una lista vinculada, siempre pasamos la dirección de lk_item al método de la lista vinculada. Obtener los datos también es un puntero a la estructura link_item, pero podemos usar la macro container_of para usar el puntero a la struct link_item devuelto por el método de lista enlazada para derivar el puntero de la struct user_link_item, para que el usuario pueda obtener los datos almacenados en la lista enlazada. La memoria de los elementos específicos en dicha implementación también es asignada por usuarios externos.

Este método es algo similar al mecanismo de herencia de C ++. Puedes considerar usar este método cuando encuentres problemas de herencia.

Contadores de referencia

Al desarrollar programas de servidor, a menudo nos encontramos con procesamiento paralelo de subprocesos múltiples. Hay un problema seguro de subprocesos con los datos a procesar. El procesamiento más seguro para subprocesos son las operaciones atómicas. Cada procesamiento se realiza utilizando una sola instrucción. El segundo es usar bloqueos de hilos para la sincronización de hilos. Para los datos almacenados en una estructura de datos, puede leerse en un hilo y los datos se eliminan en otro hilo. Podemos imitar el método utilizado por WIN32 COM para manejar este problema. Agregue un contador de referencia, aumente el contador de referencia de los datos cada vez que desee acceder a los datos. Cuando finalice el uso, disminuya el contador de referencia de los datos. Cuando el valor del contador de referencia se reduce a 0, los datos se eliminan realmente. Por supuesto, las operaciones de incremento y decremento del contador de referencia deben ser seguras para subprocesos y pueden implementarse completamente usando operaciones atómicas.

Todos los objetos de datos que necesitan ser seguros para subprocesos pueden implementarse mediante la herencia de la implementación de la macro container_of mencionada anteriormente.

Procesamiento estructural de campos de longitud variable en protocolos de comunicación

La comunicación del lado del servidor generalmente diseña un conjunto de protocolos. En el acuerdo suele ser la forma:

Copyright © Conocimiento de Windows All Rights Reserved