
Prólogo
Nuestro pequeño equipo está desarrollando dos herramientas OpenSource para desarrolladores de C ++: el marco del actor
SObjectizer y el servidor HTTP incorporado RESTinio. Sin embargo, regularmente nos encontramos con un par de preguntas no triviales:
- ¿Qué funciones agregar a la biblioteca y cuáles dejar "al agua"?
- ¿Cómo demostrar claramente las formas "ideológicamente correctas" de usar la biblioteca?
Es bueno cuando las respuestas a tales preguntas aparecen durante el uso de nuestros desarrollos en proyectos reales, cuando los desarrolladores nos llegan con sus quejas o lista de deseos. Debido a la satisfacción de los deseos de los usuarios, llenamos nuestras herramientas con una funcionalidad que es dictada por la vida misma, y no "extraída del dedo".
Pero la información nos llega lejos de todos los problemas y dificultades que enfrentan los usuarios. Y no siempre podemos usar la información recibida, y especialmente los ejemplos de código, en nuestros materiales públicos.
Por lo tanto, a veces pensamos en pequeños problemas para nosotros mismos, resolviendo lo que nos vemos obligados a convertir de desarrolladores de herramientas a usuarios. Esto nos permite mirar nuestras propias herramientas con diferentes ojos y comprender por nosotros mismos qué es bueno, qué no es bueno, qué falta y qué es demasiado.
Hoy queremos contar una de esas tareas "pequeñas", en las que SObjectizer y RESTinio se unieron naturalmente.
Escalado y distribución de imágenes. ¿Por qué exactamente esto?
Como una pequeña tarea de demostración para nosotros, elegimos un servidor HTTP que distribuye imágenes escaladas a pedido. Pones las imágenes en algún directorio, inicias el servidor HTTP, le solicitas el formulario:
curl "http://localhost:8080/my_picture.jpg?op=resize&max=1920"
y a cambio obtienes una imagen a escala de 1920 píxeles en el lado largo.
La elección recayó en esta tarea porque demuestra perfectamente los escenarios para los que en un momento comenzamos a desarrollar RESTinio: hay un código depurado y de larga ejecución en C o C ++ al que debe adjuntar una entrada HTTP y comenzar a responder a las solicitudes entrantes. Al mismo tiempo, lo cual es importante, el procesamiento de la solicitud de la solicitud puede llevar un tiempo considerable y, por lo tanto, no es rentable extraer el código de la aplicación directamente en el contexto IO. El servidor HTTP debe ser asíncrono: acepte y analice la solicitud HTTP, proporcione la solicitud analizada en algún lugar para su posterior procesamiento de la aplicación, proceda a atender la siguiente solicitud HTTP, vuelva a devolver la respuesta a la solicitud HTTP cuando alguien la prepare.
Esto es exactamente lo que sucede cuando se procesan solicitudes para escalar imágenes. Un servidor HTTP puede hacer su trabajo directo (es decir, leer datos, analizar una solicitud HTTP) en una fracción de milisegundo. Pero escalar una imagen puede tomar decenas, cientos o incluso miles de milisegundos.
Y dado que puede llevar mucho tiempo escalar una imagen, debe asegurarse de que el servidor HTTP pueda continuar funcionando mientras se escala la imagen. Para hacer esto, necesitamos difundir el trabajo del servidor HTTP y escalar imágenes a diferentes contextos de trabajo. En el caso simple, estos serán diferentes hilos de trabajo. Bueno, dado que vivimos en procesadores multi-core, tendremos varios hilos de trabajo. Algunos atenderán solicitudes HTTP, otros trabajarán con imágenes.
Resulta que para distribuir imágenes escalables a través de HTTP, tenemos que reutilizar el código C / C ++ que funciona desde hace mucho tiempo (en este caso, ImageMagic ++) y atender las solicitudes HTTP de forma asíncrona y realizar el procesamiento de las solicitudes en varios flujos de trabajo. Una excelente tarea para RESTinio y SObjectizer, como nos pareció.
Y decidimos nombrar nuestro proyecto de camarones camarones.
Camarones como es
¿Qué hace el camarón?
Shrimp se ejecuta como una aplicación de consola, se abre y escucha en el puerto especificado, recibe y procesa las solicitudes HTTP GET del formulario:
/<image>.<ext> /<image>.<ext>?op=resize&<side>=<value>
Donde:
- imagen es el nombre del archivo de imagen a escala. Por ejemplo, my_picture o DSCF0069;
- ext es una de las extensiones compatibles con camarones (jpg, jpeg, png o gif);
- lado es una indicación del lado para el que se establece el tamaño. Puede tener un valor de ancho, en este caso la imagen se escala para que el ancho resultante sea igual al valor especificado, la altura de la imagen se selecciona automáticamente mientras se mantiene la relación de aspecto. O el valor de la altura, en este caso, la escala se produce en altura. Cualquiera de los valores máximos, en este caso, el lado largo es limitado, y los camarones en sí mismos determinan si el lado largo es alto o ancho;
- valor es el tamaño en el que se produce el escalado.
Si solo se especifica el nombre del archivo en la URL, sin la operación de cambio de tamaño, entonces los camarones simplemente devuelven la imagen original en la respuesta. Si se especifica la operación de cambio de tamaño, entonces el camarón cambia el tamaño de la imagen solicitada y proporciona la versión escalada.
Al mismo tiempo, los camarones mantienen en la memoria un caché de imágenes escaladas. Si se solicita una imagen repetidamente con los mismos parámetros de cambio de tamaño, que ya está en la memoria caché, se devuelve el valor de la memoria caché. Si no hay imagen en el caché, la imagen se lee desde el disco, se escala, se almacena en el caché y se devuelve en respuesta.
El caché se borra periódicamente. Las imágenes que han vivido en el caché durante más de una hora desde el último acceso a ellas se eliminan. Además, las imágenes más antiguas se eliminan del caché si el caché excede su tamaño máximo (en un proyecto de demostración es de 100Mb).
Hemos preparado una
página para que cualquiera pueda experimentar con camarones:

En esta página puede establecer el tamaño de la imagen y hacer clic en "Cambiar tamaño". Se realizarán dos solicitudes al servidor de camarones con los mismos parámetros. Lo más probable es que la primera solicitud sea única (es decir, todavía no habrá una memoria caché con tales parámetros de cambio de tamaño en la memoria caché), por lo que la primera solicitud llevará algún tiempo para escalar la imagen. Y la segunda solicitud, muy probablemente, encontrará la imagen ya escalada en el caché y la dará de inmediato.
Es posible juzgar si una imagen se proporciona desde el caché o si realmente fue escalada por el texto debajo de la imagen. Por ejemplo, el texto "Transformado (114.0ms)" indica que la imagen fue escalada y la operación de zoom tomó 114 milisegundos.
¿Cómo lo hace el camarón?
Shrimp es una aplicación multiproceso que ejecuta tres grupos de hilos de trabajo:
- El grupo de subprocesos de trabajo que ejecuta el servidor HTTP. En este grupo, se sirven nuevas conexiones, se reciben y analizan las solicitudes entrantes, se generan y envían respuestas. El servidor HTTP se implementa a través de la biblioteca RESTinio.
- Un subproceso de trabajo independiente en el que se ejecuta el agente transform_manager SObjectizer. Este agente procesa las solicitudes recibidas del servidor HTTP y mantiene un caché de imágenes escaladas.
- El grupo de subprocesos en el que los agentes SObjectizer funcionan transformadores. Realizan el escalado real de imágenes usando ImageMagic ++.
Resulta el siguiente esquema de trabajo:

El servidor HTTP acepta la solicitud entrante, la analiza y verifica la corrección. Si esta solicitud no requiere una operación de cambio de tamaño, el servidor HTTP mismo procesa la solicitud a través de la operación
sendfile . Si la solicitud requiere una operación de cambio de tamaño, la solicitud se envía de forma asíncrona al agente transform_manager.
El agente transform_manager recibe solicitudes del servidor HTTP, verifica la presencia de imágenes ya escaladas en la memoria caché. Si hay una imagen en el caché, transform_manager genera inmediatamente una respuesta para el servidor HTTP. Si no hay imagen, transform_manager envía una solicitud para escalar la imagen a uno de los agentes transformadores. Cuando el resultado de escala proviene del transformador, el resultado se almacena en la memoria caché y se genera una respuesta para el servidor HTTP.
El agente transformador recibe solicitudes de transform_manager, las procesa y devuelve el resultado de la transformación al agente transform_manager.
¿Qué tiene camarones debajo del capó?
El código fuente de la versión más mínima de camarones que se describe en este artículo se puede encontrar en este repositorio:
camarones-demostración en BitBucket o
en GitHub .
Hay mucho código, aunque, en su mayor parte, en esta versión de camarones, el código es bastante trivial. Sin embargo, tiene sentido centrarse en algunos aspectos de la implementación.
Usando C ++ 17 y las versiones más recientes del compilador
En la implementación de camarones, decidimos usar C ++ 17 y las últimas versiones de compiladores, en particular GCC 7.3 y 8.1. El proyecto está fuertemente investigado. Por lo tanto, el conocimiento práctico de C ++ 17 en el marco de dicho proyecto es natural y aceptable. Mientras que en desarrollos más mundanos centrados en aplicaciones industriales prácticas aquí y ahora, nos vemos obligados a mirar hacia atrás a compiladores bastante antiguos y usar quizás C ++ 14, o incluso solo un subconjunto de C ++ 11.
Debo decir que C ++ 17 hace una buena impresión. Parece que no utilizamos tantas innovaciones del decimoséptimo estándar en el código del camarón, pero tuvieron un efecto positivo: el atributo [[nodiscard]], std :: opcional / std :: variant / std :: filesystem directamente " fuera de la caja ", y no de dependencias externas, enlace estructurado, si se constexpr, la capacidad de reunir visitante para lambdas para std :: visita ... Individualmente, todos estos son pequeños, pero juntos producen un poderoso efecto acumulativo.
Entonces, el primer resultado útil que obtuvimos al desarrollar camarones: C ++ 17 vale la pena cambiar a él.
Servidor HTTP utilizando herramientas RESTinio
Quizás la parte más fácil de los camarones resultó ser el servidor HTTP y el controlador de solicitudes HTTP GET (
http_server.hpp y
http_server.cpp ).
Recibir y enviar solicitudes entrantes
Esencialmente, toda la lógica básica del servidor HTTP de camarones se concentra en esta función:
void add_transform_op_handler( const app_params_t & app_params, http_req_router_t & router, so_5::mbox_t req_handler_mbox ) { router.http_get( R"(/:path(.*)\.:ext(.{3,4}))", restinio::path2regex::options_t{}.strict( true ), [req_handler_mbox, &app_params]( auto req, auto params ) { if( has_illegal_path_components( req->header().path() ) ) { return do_400_response( std::move( req ) ); } const auto opt_image_format = image_format_from_extension( params[ "ext" ] ); if( !opt_image_format ) { return do_400_response( std::move( req ) ); } if( req->header().query().empty() ) { return serve_as_regular_file( app_params.m_storage.m_root_dir, std::move( req ), *opt_image_format ); } const auto qp = restinio::parse_query( req->header().query() ); if( "resize" != restinio::value_or( qp, "op"sv, ""sv ) ) { return do_400_response( std::move( req ) ); } handle_resize_op_request( req_handler_mbox, *opt_image_format, qp, std::move( req ) ); return restinio::request_accepted(); } ); }
Esta función prepara el controlador de solicitud HTTP GET utilizando el
enrutador RESTinio
ExpressJS . Cuando el servidor HTTP recibe una solicitud GET, cuya URL se encuentra bajo la expresión regular dada, se llama a la función lambda especificada.
Esta función lambda realiza algunas comprobaciones simples sobre la exactitud de la solicitud, pero en general, su trabajo se reduce a una elección simple: si no se establece el cambio de tamaño, la imagen solicitada se devolverá en su forma original utilizando un sistema de envío efectivo del sistema. Si se establece el modo de cambio de tamaño, se genera un mensaje y se envía al agente transform_manager:
void handle_resize_op_request( const so_5::mbox_t & req_handler_mbox, image_format_t image_format, const restinio::query_string_params_t & qp, restinio::request_handle_t req ) { try_to_handle_request( [&]{ auto op_params = transform::resize_params_t::make( restinio::opt_value< std::uint32_t >( qp, "width" ), restinio::opt_value< std::uint32_t >( qp, "height" ), restinio::opt_value< std::uint32_t >( qp, "max" ) ); transform::resize_params_constraints_t{}.check( op_params ); std::string image_path{ req->header().path() }; so_5::send< so_5::mutable_msg<a_transform_manager_t::resize_request_t>>( req_handler_mbox, std::move(req), std::move(image_path), image_format, op_params ); }, req ); }
Resulta que el servidor HTTP, después de haber aceptado la solicitud de cambio de tamaño, se la entrega al agente transform_manager a través de un mensaje asíncrono y continúa atendiendo otras solicitudes.
Compartir archivos con sendfile
Si el servidor HTTP detecta una solicitud para la imagen original, sin la operación de cambio de tamaño, el servidor envía inmediatamente esta imagen a través de la operación sendfile. El código principal asociado con esto es el siguiente (el código completo para esta función se puede encontrar
en el repositorio ):
[[nodiscard]] restinio::request_handling_status_t serve_as_regular_file( const std::string & root_dir, restinio::request_handle_t req, image_format_t image_format ) { const auto full_path = make_full_path( root_dir, req->header().path() ); try { auto sf = restinio::sendfile( full_path ); ... return set_common_header_fields_for_image_resp( file_stat.st_mtim.tv_sec, resp ) .append_header( restinio::http_field::content_type, image_content_type_from_img_format( image_format ) ) .append_header( http_header::shrimp_image_src, image_src_to_str( http_header::image_src_t::sendfile ) ) .set_body( std::move( sf ) ) .done(); } catch(...) {} return do_404_response( std::move( req ) ); }
El punto clave aquí es llamar a
restinio :: sendfile () y luego pasar el valor devuelto por esta función a set_body ().
La función restinio :: sendfile () crea una operación de carga de archivos utilizando la API del sistema. Cuando esta operación se pasa a set_body (), RESTinio comprende que el contenido del archivo especificado en restinio :: sendfile () se utilizará para el cuerpo de la respuesta HTTP. Luego usa la API del sistema para escribir el contenido de este archivo en el socket TCP.
Implementando el caché de imágenes
El agente transform_manager almacena el caché de las imágenes convertidas, donde las imágenes se colocan después de escalar. Este caché es un contenedor simple hecho a sí mismo que proporciona acceso a su contenido de dos maneras:
- Al buscar un elemento por clave (similar a cómo sucede esto en los contenedores estándar std :: map y std :: unordered_map).
- Al acceder al elemento de caché más antiguo.
El primer método de acceso se usa cuando necesitamos verificar la disponibilidad de la imagen en el caché. El segundo es cuando eliminamos las imágenes más antiguas del caché.
No comenzamos a buscar algo listo para estos fines en Internet. Probablemente Boost.MultiIndex sería bastante adecuado aquí. Pero no quería arrastrar a Boost solo por MultiIndex, así que hicimos
nuestra implementación trivial literalmente de rodillas. Parece funcionar;)
Cola de solicitudes pendientes en transform_manager
El agente transform_manager, a pesar de su tamaño bastante decente (un
archivo hpp de aproximadamente 250 líneas y un
archivo cpp de aproximadamente 270 líneas), en nuestra implementación más simple de camarones, resultó ser bastante trivial, en nuestra opinión.
Uno de los puntos que hace una contribución significativa a la complejidad y el volumen del código del agente es la presencia en transform_manager no solo del caché de las imágenes transformadas, sino también de la cola de solicitudes pendientes.
Tenemos un número limitado de agentes transformadores (en principio, su número debería corresponder aproximadamente al número de núcleos de procesamiento disponibles). Si llegan más solicitudes simultáneamente que hay transformadores libres, entonces podemos responder inmediatamente negativamente a la solicitud o poner en cola la solicitud. Y luego retírelo de la cola cuando aparezca un transformador libre.
En camarones, utilizamos una cola de solicitudes en espera, que se define de la siguiente manera:
struct pending_request_t { transform::resize_request_key_t m_key; sobj_shptr_t<resize_request_t> m_cmd; std::chrono::steady_clock::time_point m_stored_at; pending_request_t( transform::resize_request_key_t key, sobj_shptr_t<resize_request_t> cmd, std::chrono::steady_clock::time_point stored_at ) : m_key{ std::move(key) } , m_cmd{ std::move(cmd) } , m_stored_at{ stored_at } {} }; using pending_request_queue_t = std::queue<pending_request_t>; pending_request_queue_t m_pending_requests; static constexpr std::size_t max_pending_requests{ 64u };
Una vez recibida la solicitud, la ponemos en la cola para fijar el momento en que se recibió la solicitud. Luego verificamos periódicamente para ver si el tiempo de espera para esta solicitud ha expirado. De hecho, en principio, puede suceder que un paquete de solicitudes "pesadas" haya llegado antes, cuyo procesamiento tomó demasiado tiempo. Es incorrecto esperar sin cesar a que aparezca un transformador libre, es mejor enviar una respuesta negativa al cliente después de un tiempo, lo que significa que el servicio ahora está sobrecargado.
También hay un límite de tamaño para la cola de solicitudes pendientes. Si la cola ya ha alcanzado su tamaño máximo, inmediatamente nos negamos a procesar la solicitud y le decimos al cliente que estamos sobrecargados.
Hay un punto importante relacionado con la cola de solicitudes pendientes, en el que nos centraremos en la conclusión del artículo.
Escriba sobj_shptr_t y reutilice instancias de mensajes
Al determinar el tipo de la cola de solicitudes en espera, así como en las firmas de algunos métodos de transform_manager, puede ver el uso del tipo sobj_shptr_t. Tiene sentido detenerse en más detalles sobre qué tipo es y por qué se usa.
La conclusión es que transform_manager recibe una solicitud del servidor HTTP como un mensaje resize_request_t:
struct resize_request_t final : public so_5::message_t { restinio::request_handle_t m_http_req; std::string m_image; image_format_t m_image_format; transform::resize_params_t m_params; resize_request_t( restinio::request_handle_t http_req, std::string image, image_format_t image_format, transform::resize_params_t params ) : m_http_req{ std::move(http_req) } , m_image{ std::move(image) } , m_image_format{ image_format } , m_params{ params } {} };
y tenemos que hacer algo para almacenar esta información en la cola de solicitudes en espera. Por ejemplo, puede crear una nueva instancia de resize_request_t y mover los valores del mensaje recibido al mismo.
Y puede recordar que el mensaje en sí mismo en SObjectizer es un objeto creado dinámicamente. Y no es un objeto simple, sino con un contador de enlaces dentro. Y que en SObjectizer hay un tipo especial de puntero inteligente para tales objetos: intrusive_ptr_t.
Es decir no podemos hacer una copia de resize_request_t para la cola de solicitudes en espera, pero simplemente podemos poner en esta cola un puntero inteligente a una instancia existente de resize_request_t. Lo que hacemos Y para no escribir en todas partes el nombre bastante exótico so_5 :: intrusive_ptr_t, ingresamos nuestro alias:
template<typename T> using sobj_shptr_t = so_5::intrusive_ptr_t<T>;
Respuestas asincrónicas a clientes
Dijimos que las solicitudes HTTP se procesan de forma asincrónica. Y mostramos anteriormente cómo el servidor HTTP envía una consulta al agente transform_manager con un mensaje asincrónico. Pero, ¿qué pasa con las respuestas a las solicitudes HTTP?
Las respuestas también se sirven de forma asincrónica. Por ejemplo, en el código transform_manager puede ver lo siguiente:
void a_transform_manager_t::on_failed_resize( failed_resize_t & , sobj_shptr_t<resize_request_t> cmd ) { do_404_response( std::move(cmd->m_http_req) ); }
Este código genera una respuesta negativa a la solicitud HTTP en el caso de que la imagen no se pueda escalar por algún motivo. La respuesta se genera en la función auxiliar do_404_response, cuyo código se puede representar de la siguiente manera:
auto do_404_response( restinio::request_handle_t req ) { auto resp = req->create_response( 404, "Not Found" ); resp.append_header( restinio::http_field_t::server, "Shrimp draft server" ); resp.append_header_date_field(); if( req->header().should_keep_alive() ) resp.connection_keep_alive(); else resp.connection_close(); return resp.done(); }
El primer punto clave con do_404_response () es que esta función se llama en el contexto de trabajo del agente transform_manager y no en el contexto de trabajo del servidor HTTP.
El segundo punto clave es la llamada al método done () en el objeto resp totalmente formado. Toda la magia asincrónica con una respuesta HTTP ocurre aquí. El método done () toma toda la información preparada en resp y la envía asincrónicamente al servidor HTTP. Es decir Se producirá un retorno de do_404_response () inmediatamente después de que el contenido del objeto resp esté en cola por el servidor HTTP.
El servidor HTTP en su contexto de trabajo detectará la presencia de una nueva respuesta HTTP y comenzará a realizar las acciones necesarias para enviar la respuesta al cliente apropiado.
Escriba datasizable_blob_t
Otro pequeño punto que tiene sentido aclarar, porque probablemente es incomprensible sin comprender las complejidades de RESTinio. Estamos hablando de la presencia de, a primera vista, un tipo extraño de datasizeable_blob_t, definido de la siguiente manera:
struct datasizable_blob_t : public std::enable_shared_from_this< datasizable_blob_t > { const void * data() const noexcept { return m_blob.data(); } std::size_t size() const noexcept { return m_blob.length(); } Magick::Blob m_blob;
Para explicar por qué se necesita este tipo, debe mostrar cómo se forma una respuesta HTTP con una imagen transformada:
void serve_transformed_image( restinio::request_handle_t req, datasizable_blob_shared_ptr_t blob, image_format_t img_format, http_header::image_src_t image_src, header_fields_list_t header_fields ) { auto resp = req->create_response(); set_common_header_fields_for_image_resp( blob->m_last_modified_at, resp ) .append_header( restinio::http_field::content_type, image_content_type_from_img_format( img_format ) ) .append_header( http_header::shrimp_image_src, image_src_to_str( image_src ) ) .set_body( std::move( blob ) ); for( auto & hf : header_fields ) { resp.append_header( std::move( hf.m_name ), std::move( hf.m_value ) ); } resp.done(); }
Prestamos atención a la llamada a set_body (): un puntero inteligente a la instancia datasizable_blob_t se envía directamente allí. Por qué
El hecho es que
RESTinio admite varias opciones para formar el cuerpo de una respuesta HTTP . Lo más simple es pasar una instancia de tipo std :: string a set_body () y RESTinio guardará el valor de esta cadena dentro del objeto resp.
Pero hay momentos en que el valor de set_body () debe reutilizarse en varias respuestas a la vez. Por ejemplo, en el camarón esto sucede cuando el camarón recibe varias solicitudes idénticas para la transformación de la misma imagen.
En este caso, no es rentable copiar el mismo valor en cada respuesta. Por lo tanto, en RESTinio hay una variante set_body () de la forma: template<typename T> auto set_body(std::shared_ptr<T> body);
Pero en este caso, se impone una limitación importante sobre el tipo T: debe contener los métodos de datos públicos () y tamaño (), que son necesarios para que RESTinio pueda acceder al contenido de la respuesta.La imagen escalada en camarones se almacena como un objeto Magick :: Blob. Hay un método de datos en el tipo Magic :: Blob, pero no hay un método size (), pero sí un método length (). Por lo tanto, necesitábamos la clase de contenedor datasizable_blob_t, que proporciona a RESTinio la interfaz necesaria para acceder al valor de Magick :: Blob.Mensajes periódicos en transform_manager
El agente transform_manager necesita hacer varias cosas de vez en cuando:- Extraiga las imágenes que han estado en el caché durante demasiado tiempo desde el caché.
- controle el tiempo que pasan las solicitudes en la cola de espera de los transformadores libres.
El agente transform_manager realiza estas acciones a través de mensajes periódicos. Se ve de la siguiente manera.Primero, se determinan los tipos de señales que se utilizarán como mensajes periódicos: struct clear_cache_t final : public so_5::signal_t {}; struct check_pending_requests_t final : public so_5::signal_t {};
Luego, el agente se suscribe, incluidas estas señales: void a_transform_manager_t::so_define_agent() { so_subscribe_self() .event( &a_transform_manager_t::on_resize_request ) .event( &a_transform_manager_t::on_resize_result ) .event( &a_transform_manager_t::on_clear_cache ) .event( &a_transform_manager_t::on_check_pending_requests ); } void a_transform_manager_t::on_clear_cache( mhood_t<clear_cache_t> ) {...} void a_transform_manager_t::on_check_pending_requests( mhood_t<check_pending_requests_t> ) {...}
Gracias a la suscripción, SObjectizer llamará al controlador deseado cuando el agente reciba la señal correspondiente.Y solo queda ejecutar mensajes periódicos cuando se inicia el agente: void a_transform_manager_t::so_evt_start() { m_clear_cache_timer = so_5::send_periodic<clear_cache_t>( *this, clear_cache_period, clear_cache_period ); m_check_pending_timer = so_5::send_periodic<check_pending_requests_t>( *this, check_pending_period, check_pending_period ); }
El punto clave aquí es guardar timer_id, que son devueltos por las funciones send_periodic (). Después de todo, una señal periódica solo llegará mientras su timer_id esté vivo. Por lo tanto, si el valor de retorno de send_periodic () no se guarda, el envío de un mensaje periódico se cancelará de inmediato. Por lo tanto, la clase a_transform_manager_t tiene los siguientes atributos: so_5::timer_id_t m_clear_cache_timer; so_5::timer_id_t m_check_pending_timer;
Fin de la primera parte.
Hoy presentamos al lector la implementación más simple y minimalista de camarones. Esta implementación es suficiente para mostrar cómo RESTinio y SObjectizer se pueden usar juntos para algo más o menos como una tarea real, en lugar de un simple HelloWorld. Pero tiene una serie de fallas graves.Por ejemplo, en el agente transform_manager hay una cierta comprobación de unicidad de la solicitud. Pero solo funciona si la imagen transformada ya está en el caché. Si todavía no hay una imagen en el caché y al mismo tiempo llegan dos solicitudes idénticas para la misma imagen, ambas solicitudes se enviarán para su procesamiento. Lo que no es bueno Sería correcto procesar solo uno de ellos y posponer el segundo hasta que se complete el procesamiento del primero.Tal control más avanzado sobre la unicidad de las solicitudes conduciría a un código transform_manager mucho más complejo y voluminoso. Por lo tanto, no comenzamos a implementarlo de inmediato, sino que decidimos seguir el camino evolutivo, de simple a complejo.Además, la versión más simple de camarones es una "caja negra" que no muestra signos de su trabajo. Lo cual no es muy conveniente tanto durante la prueba como durante la operación. Por lo tanto, en el buen sentido, los camarones también deberían agregar la tala.Trataremos de eliminar estas y otras deficiencias de la primera versión de camarones en futuras versiones y las describiremos en futuros artículos. Así que estad atentos.Si alguien tiene preguntas sobre la lógica del camarón, RESTinio o SObjectizer, estaremos encantados de responder en los comentarios. Además, el camarón en sí es un proyecto de demostración, pero si alguien está interesado en su funcionalidad y le gustaría ver en el camarón algo más que la operación de cambio de tamaño, háganos saber, estaremos encantados de escuchar cualquier idea constructiva. .Continuará ...