Tutorial oficial de Boost asio

7.1 Descripción general
 
 Este capítulo presenta la biblioteca Asio de Boost C ++, que es el núcleo de la entrada y salida asíncronas. El nombre en sí lo dice todo: Asio significa entrada / salida asíncrona. Esta biblioteca permite que C ++ procese datos de forma asincrónica y es independiente de la plataforma. El procesamiento de datos asincrónico significa que no es necesario esperar a que las tareas se completen después de que se activen. En cambio, Boost.Asio activará una aplicación cuando se complete la tarea. La principal ventaja de las tareas asincrónicas es que no necesitan bloquear la aplicación mientras esperan que se complete la tarea y pueden realizar otras tareas.
 
 Un ejemplo típico de tareas asincrónicas son las aplicaciones de red. Si los datos se envían, por ejemplo, a Internet, normalmente es necesario saber si los datos se envían correctamente. Si no hay una biblioteca como Boost.Asio, se debe evaluar el valor de retorno de la función. Sin embargo, esto requiere esperar hasta que se hayan enviado todos los datos y se obtenga una confirmación o un código de error. Con Boost.Asio, este proceso se divide en dos pasos separados: El primer paso es iniciar la transmisión de datos como una tarea asincrónica. Una vez finalizada la transferencia, independientemente del éxito o error, se notificará a la aplicación sobre el resultado correspondiente en el segundo paso. La principal diferencia es que la aplicación no necesita bloquearse hasta que se complete la transferencia, pero puede realizar otras operaciones durante este tiempo.
 7.2. Servicios de E / S y objetos de E / S
 
 Las aplicaciones que utilizan Boost.Asio para el procesamiento de datos asincrónico se basan en dos conceptos: servicios de E / S y objetos de E / S. El servicio de E / S abstrae la interfaz del sistema operativo, lo que permite el procesamiento de datos asincrónico por primera vez, mientras que el objeto de E / S se utiliza para inicializar operaciones específicas. Dado que Boost.Asio solo proporciona una clase llamada boost :: asio :: io_service como servicio de E / S, implementa clases optimizadas para cada sistema operativo compatible.Además, la biblioteca también contiene varias clases de objetos de E / S. Entre ellos, la clase boost :: asio :: ip :: tcp :: socket se usa para enviar y recibir datos a través de la red, y la clase boost :: asio :: deadline_timer proporciona un temporizador para medir la llegada de un determinado fijo punto de tiempo O ha pasado un período de tiempo específico. El temporizador se usa en el primer ejemplo a continuación, porque en comparación con otros objetos de E / S proporcionados por Asio, no requiere ningún conocimiento sobre programación de red.

#include <boost/asio.hpp> 
#include <iostream> 
 
void handler(const boost::system::error_code &ec) 
{ 
   std::cout << "5 s." << std::endl; 
} 
 
int main() 
{ 
   boost::asio::io_service io_service; 
   boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5)); 
   timer.async_wait(handler); 
   io_service.run(); 
} 

La función main () primero define un servicio de E / S io_service, que se utiliza para inicializar el temporizador del objeto de E / S. Al igual que boost :: asio :: deadline_timer, todos los objetos de E / S generalmente requieren un servicio de E / S como primer parámetro de su constructor. Debido a que el temporizador funciona como un reloj de alarma, el constructor de boost :: asio :: deadline_timer puede pasar un segundo parámetro para indicar que la alarma se detiene en un momento determinado o después de un período de tiempo determinado. El ejemplo anterior especifica una duración de cinco segundos y el reloj de alarma comienza a cronometrar inmediatamente después de que se define el temporizador.
 
 Aunque podemos llamar a una función que regresa después de cinco segundos, llamando al método async_wait () y pasando el nombre de la función handler () como único parámetro, Asio puede iniciar una operación asincrónica. Tenga en cuenta que solo pasamos el nombre de la función handler () y no se llamó a la función en sí.
 
 La ventaja de async_wait () es que la llamada a la función regresa inmediatamente en lugar de esperar cinco segundos. Una vez transcurrida la hora de la alarma, la función proporcionada como parámetro se llamará en consecuencia. Por lo tanto, la aplicación puede realizar otras operaciones después de llamar a async_wait () en lugar de bloquear aquí.
 
 Los métodos como async_wait () se denominan no bloqueantes. Los objetos de E / S generalmente también proporcionan métodos de bloqueo que permiten que el flujo de ejecución permanezca bloqueado hasta que se complete una operación específica. Por ejemplo, puede llamar al método de bloqueo wait () en lugar de boost :: asio :: deadline_timer. Debido a que bloquea la llamada, no necesita pasar un nombre de función, pero regresa después de un punto de tiempo especificado o un período de tiempo especificado.
 
 Al volver a mirar el código fuente anterior, puede observar que después de llamar a async_wait (), se llama a un método llamado run () en el servicio de E / S. Esto es necesario porque el control debe ser asumido por el sistema operativo antes de que se pueda llamar a la función handler () después de cinco segundos.
 
 async_wait () iniciará una operación asincrónica y regresará inmediatamente, mientras que run () está bloqueando. Por lo tanto, la ejecución del programa se detendrá después de llamar a run (). La ironía es que muchos sistemas operativos solo admiten operaciones asincrónicas mediante funciones de bloqueo. El siguiente ejemplo muestra por qué esta limitación no suele ser un problema.

La función main () primero define un servicio de E / S io_service, que se utiliza para inicializar el temporizador del objeto de E / S. Al igual que boost :: asio :: deadline_timer, todos los objetos de E / S generalmente requieren un servicio de E / S como primer parámetro de su constructor. Debido a que el temporizador funciona como un reloj de alarma, el constructor de boost :: asio :: deadline_timer puede pasar un segundo parámetro para indicar que la alarma se detiene en un momento determinado o después de un período de tiempo determinado. El ejemplo anterior especifica una duración de cinco segundos y el reloj de alarma comienza a cronometrar inmediatamente después de que se define el temporizador.
 
 Aunque podemos llamar a una función que regresa después de cinco segundos, llamando al método async_wait () y pasando el nombre de la función handler () como único parámetro, Asio puede iniciar una operación asincrónica. Tenga en cuenta que solo pasamos el nombre de la función handler () y no se llamó a la función en sí.
 
 La ventaja de async_wait () es que la llamada a la función regresa inmediatamente en lugar de esperar cinco segundos. Una vez transcurrida la hora de la alarma, la función proporcionada como parámetro se llamará en consecuencia. Por lo tanto, la aplicación puede realizar otras operaciones después de llamar a async_wait () en lugar de bloquear aquí.
 
 Los métodos como async_wait () se denominan no bloqueantes. Los objetos de E / S generalmente también proporcionan métodos de bloqueo que permiten que el flujo de ejecución permanezca bloqueado hasta que se complete una operación específica. Por ejemplo, puede llamar al método de bloqueo wait () en lugar de boost :: asio :: deadline_timer. Debido a que bloquea la llamada, no necesita pasar un nombre de función, pero regresa después de un punto de tiempo especificado o un período de tiempo especificado.
 
 Al volver a mirar el código fuente anterior, puede observar que después de llamar a async_wait (), se llama a un método llamado run () en el servicio de E / S. Esto es necesario porque el control debe ser asumido por el sistema operativo antes de que se pueda llamar a la función handler () después de cinco segundos.
 
 async_wait () iniciará una operación asincrónica y regresará inmediatamente, mientras que run () está bloqueando. Por lo tanto, la ejecución del programa se detendrá después de llamar a run (). La ironía es que muchos sistemas operativos solo admiten operaciones asincrónicas mediante funciones de bloqueo. El siguiente ejemplo muestra por qué esta limitación no suele ser un problema.
 

#include <boost/asio.hpp> 
#include <iostream> 
 
void handler1(const boost::system::error_code &ec) 
{ 
   std::cout << "5 s." << std::endl; 
} 
 
void handler2(const boost::system::error_code &ec) 
{ 
   std::cout << "10 s." << std::endl; 
} 
 
int main() 
{ 
   boost::asio::io_service io_service; 
   boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(5)); 
   timer1.async_wait(handler1); 
   boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(10)); 
   timer2.async_wait(handler2); 
   io_service.run(); 
} 

El programa anterior utiliza dos objetos de E / S de tipo boost :: asio :: deadline_timer. El primer objeto de E / S representa una alarma que se activa después de cinco segundos, mientras que el segundo representa una alarma que se activa después de diez segundos. Después de que haya pasado cada período de tiempo especificado, las funciones handler1 () y handler2 () se llamarán en consecuencia.

Al final de main (), se vuelve a llamar al método run () en el único servicio de E / S. Como se mencionó anteriormente, esta función bloqueará la ejecución y pasará el control al sistema operativo para que se haga cargo del procesamiento asincrónico. Con la ayuda del sistema operativo, la función handler1 () se llamará después de cinco segundos, y la función handler2 () se llamará después de diez segundos.

A primera vista, puede que le resulte un poco extraño por qué el procesamiento asincrónico llama al método de bloqueo run (). Sin embargo, dado que la aplicación debe evitar que se cancele la ejecución, en realidad no hay ningún problema al hacerlo. Si run () no bloquea, main () finalizará y terminará la aplicación. Si la aplicación no debe bloquearse, entonces se debe llamar a run () en un nuevo hilo y, naturalmente, solo bloqueará ese hilo.

Una vez que se completan todas las operaciones asincrónicas de un servicio de E / S en particular, el control se devuelve al método run () y luego regresa. En los dos ejemplos anteriores, la aplicación finalizará tan pronto como expire el tiempo de alarma.

7.3. Escalabilidad y multiproceso

El uso de bibliotecas como Boost.Asio para desarrollar aplicaciones es diferente del estilo general de C ++. Aquellas funciones que pueden tardar mucho en regresar ya no se llaman de forma secuencial. En lugar de llamar a las funciones de bloqueo, Boost.Asio inicia una operación asincrónica. Las funciones que deben llamarse una vez finalizada la operación se implementan como identificadores correspondientes. La desventaja de este método es que las funciones que se ejecutaron originalmente de forma secuencial se han separado físicamente, lo que dificulta la comprensión del código correspondiente.

Las bibliotecas como Boost.Asio se utilizan generalmente para hacer que las aplicaciones sean más eficientes. La aplicación no necesita esperar a que se complete una función específica, pero puede realizar otras tareas mientras tanto, como iniciar otra operación que lleve mucho tiempo.

La escalabilidad se refiere a la capacidad de una aplicación para obtener beneficios de manera efectiva de nuevos recursos. Si esas operaciones con un tiempo de ejecución prolongado no deben bloquear otras operaciones, entonces se recomienda utilizar Boost.Asio. Dado que las PC actuales suelen tener procesadores de varios núcleos, las aplicaciones con subprocesos pueden mejorar aún más el rendimiento de una aplicación basada en Boost.Asio .

Si se llama al método run () en un objeto de tipo boost :: asio :: io_service, el identificador asociado también se ejecutará en el mismo hilo. Al utilizar varios subprocesos, una aplicación puede llamar a varios métodos run () al mismo tiempo. Una vez que finaliza una operación asincrónica, el servicio de E / S correspondiente ejecutará el identificador en uno de estos subprocesos. Si la segunda operación finaliza poco después de la primera, el servicio de E / S puede ejecutar el identificador en otro subproceso sin esperar a que termine el primer identificador.

 #include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <iostream> 
 
 void handler1(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 void handler2(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 boost::asio::io_service io_service; 
 
 void run() 
 { 
   io_service.run(); 
 } 
 
 int main() 
 { 
   boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(5)); 
   timer1.async_wait(handler1); 
   boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(5)); 
   timer2.async_wait(handler2); 
   boost::thread thread1(run); 
   boost::thread thread2(run); 
   thread1.join(); 
   thread2.join(); 
 } 

El ejemplo de la sección anterior es ahora una aplicación multiproceso. Al usar la clase boost :: thread definida en boost / thread.hpp, que proviene de la biblioteca Boost C ++ Thread, creamos dos hilos en main (). Ambos subprocesos llaman al método run () para el mismo servicio de E / S. De esta forma, cuando se completa la operación asincrónica, el servicio de E / S puede utilizar dos subprocesos para ejecutar la función de control.

Los dos temporizadores de este ejemplo están configurados para activarse después de cinco segundos. Dado que hay dos subprocesos, handler1 () y handler2 () se pueden ejecutar simultáneamente. Si el primero aún se está ejecutando cuando se activa el segundo temporizador, el segundo identificador se ejecutará en el segundo hilo. Si el identificador del primer temporizador ha expirado, el servicio de E / S es libre de elegir cualquier hilo.

Los subprocesos pueden mejorar el rendimiento de la aplicación. Debido a que los subprocesos se ejecutan en núcleos de procesador, no tiene sentido crear más subprocesos que núcleos. Esto asegura que cada subproceso se ejecute en su propio núcleo sin competir con otros subprocesos en el mismo núcleo.

Cabe señalar que el uso de subprocesos no siempre vale la pena. La operación del ejemplo anterior dará como resultado una salida mixta de información diferente en el flujo de salida estándar, porque los dos identificadores pueden ejecutarse en paralelo, accediendo al mismo recurso compartido: el flujo de salida estándar std :: cout. Este acceso debe sincronizarse para garantizar que cada pieza de información esté completamente escrita antes de que otro hilo pueda escribir otra pieza de información en el flujo de salida estándar. El uso de subprocesos en esta situación no proporciona muchos beneficios si los identificadores individuales no pueden ejecutarse de forma independiente en paralelo.

Llamar al método run () del mismo servicio de E / S varias veces es la forma recomendada de aumentar la escalabilidad para las aplicaciones basadas en Boost.Asio. También hay un enfoque diferente: en lugar de vincular varios subprocesos a un solo servicio de E / S, cree varios servicios de E / S. Luego, cada servicio de E / S usa un hilo. Si el número de servicios de E / S coincide con el número de núcleos de procesador en el sistema, se pueden realizar operaciones asincrónicas en sus respectivos núcleos.


 #include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <iostream> 
 
 void handler1(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 void handler2(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 boost::asio::io_service io_service1; 
 boost::asio::io_service io_service2; 
 
 void run1() 
 { 
   io_service1.run(); 
 } 
 
 void run2() 
 { 
   io_service2.run(); 
 } 
 
 int main() 
 { 
   boost::asio::deadline_timer timer1(io_service1, boost::posix_time::seconds(5)); 
   timer1.async_wait(handler1); 
   boost::asio::deadline_timer timer2(io_service2, boost::posix_time::seconds(5)); 
   timer2.async_wait(handler2); 
   boost::thread thread1(run1); 
   boost::thread thread2(run2); 
   thread1.join(); 
   thread2.join(); 
 } 

El ejemplo anterior de usar dos temporizadores se reescribió para usar dos servicios de E / S. La aplicación todavía se basa en dos subprocesos, pero ahora cada subproceso está vinculado a un servicio de E / S diferente. Además, los dos objetos de E / S timer1 y timer2 ahora también están vinculados a diferentes servicios de E / S.
 
 La función de esta aplicación es la misma que la anterior. Es beneficioso utilizar varios servicios de E / S en determinadas condiciones. Cada servicio de E / S tiene su propio hilo, preferiblemente ejecutándose en su propio núcleo de procesador, de modo que se pueda utilizar cada operación asincrónica y su identificador. Ejecución localizada. Si no hay datos remotos o funciones para acceder, entonces cada servicio de E / S es como una pequeña aplicación autónoma. Local y remoto aquí se refieren a recursos como cachés y páginas de memoria. Debido a que necesita tener un conocimiento especial del hardware subyacente, el sistema operativo, el compilador y los posibles cuellos de botella antes de determinar la estrategia de optimización, solo debe usar varios servicios de E / S cuando conozca estos beneficios.
 7.4 Programación en red
 
 Aunque Boost.Asio es una biblioteca que puede procesar cualquier tipo de datos de forma asincrónica, se utiliza principalmente para la programación en red. Esto se debe al hecho de que Boost.Asio ya ha admitido funciones de red mucho antes de agregar otros objetos de E / S. La función de red es un buen ejemplo de procesamiento asincrónico, porque la transmisión de datos a través de la red puede llevar mucho tiempo, por lo que no se pueden obtener directamente condiciones de confirmación o error.
 
 Boost.Asio proporciona múltiples objetos de E / S para desarrollar aplicaciones de red. El siguiente ejemplo usa la clase boost :: asio :: ip :: tcp :: socket para establecer una conexión con otra PC y descargar la página de inicio de 'Highscore'; justo como lo que necesita un navegador cuando apunta a www.highscore.de.

#include <boost/asio.hpp> 
#include <boost/array.hpp> 
#include <iostream> 
#include <string> 

boost::asio::io_service io_service; 
boost::asio::ip::tcp::resolver resolver(io_service); 
boost::asio::ip::tcp::socket sock(io_service); 
boost::array<char, 4096> buffer; 

void read_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
{ 
  if (!ec) 
  { 
    std::cout << std::string(buffer.data(), bytes_transferred) << std::endl; 
    sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
  } 
} 

void connect_handler(const boost::system::error_code &ec) 
{ 
  if (!ec) 
  { 
    boost::asio::write(sock, boost::asio::buffer("GET / HTTP 1.1\r\nHost: highscore.de\r\n\r\n")); 
    sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
  } 
} 

void resolve_handler(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it) 
{ 
  if (!ec) 
  { 
    sock.async_connect(*it, connect_handler); 
  } 
} 

int main() 
{ 
  boost::asio::ip::tcp::resolver::query query("www.highscore.de", "80"); 
  resolver.async_resolve(query, resolve_handler); 
  io_service.run(); 
} 

La parte más obvia de este programa es el uso de tres identificadores: las funciones connect_handler () y read_handler () serán llamadas después de que se establezca la conexión y se reciban los datos, respectivamente. Entonces, ¿por qué necesita la función resolve_handler ()?
 
 Internet utiliza las llamadas direcciones IP para identificar cada PC. La dirección IP es en realidad solo una larga cadena de números, difícil de recordar. Es mucho más fácil recordar nombres como www.highscore.de. Para utilizar nombres similares en Internet, es necesario traducirlos a las direcciones IP correspondientes mediante un proceso llamado resolución de nombres de dominio. Este proceso se completa con el llamado resolutor de nombres de dominio, y el objeto de E / S correspondiente es: boost :: asio :: ip :: tcp :: resolver.
 
 La resolución de nombres de dominio también es un proceso que requiere conectarse a Internet. Algunas PC especializadas, llamadas servidores DNS, actúan como una guía telefónica, ya que sabe qué dirección IP está asignada a qué PC. Porque el proceso en sí es transparente, siempre que comprenda el concepto detrás de él y por qué necesita objetos de E / S boost :: asio :: ip :: tcp :: resolver. Dado que la resolución de nombres de dominio no ocurre localmente, también se implementa como una operación asincrónica. Una vez que la resolución del nombre de dominio sea exitosa o se interrumpa por un error, se llamará a la función resolve_handler ().
 
 Debido a que la recepción de datos requiere una conexión exitosa, que a su vez requiere una resolución exitosa del nombre de dominio, estas tres operaciones asincrónicas diferentes deben iniciarse con tres identificadores diferentes. resolve_handler () accede al sock del objeto de E / S y crea una conexión con la dirección resuelta proporcionada por el iterador. Y sock también se usa internamente en connect_handler () para enviar solicitudes HTTP e iniciar la recepción de datos. Como todas estas operaciones son asincrónicas, el nombre de cada identificador se pasa como parámetro. Dependiendo de cada identificador, se requieren otros parámetros correspondientes, como un iterador que apunta a la dirección resuelta o el búfer utilizado para guardar los datos recibidos.
 
 Después de comenzar a ejecutarse, la aplicación creará una consulta de objeto de tipo boost :: asio :: ip :: tcp :: resolver :: query, que representa una consulta, que contiene el nombre www.highscore.de y el puerto 80 comúnmente utilizado En Internet. Esta consulta se pasa al método async_resolve () para resolver el nombre. Finalmente, main () solo necesita llamar al método run () del servicio de E / S y transferir el control al sistema operativo para una operación asincrónica.
 
 Cuando se completa el proceso de resolución del nombre de dominio, se llama a resolve_handler () para verificar si el nombre de dominio se puede resolver. Si el análisis tiene éxito, el objeto ec con la condición de error se establece en 0. Solo en este caso se accederá al socket en consecuencia para crear una conexión. La dirección del servidor es proporcionada por el segundo parámetro de tipo boost :: asio :: ip :: tcp :: resolver :: iterator.
 
 Después de llamar al método async_connect (), connect_handler () se llamará automáticamente. Dentro del asa, se accede al objeto ec para comprobar si se ha establecido la conexión. Si la conexión es válida, llame al método async_read_some () en el conector correspondiente para iniciar la operación de lectura de datos. Para guardar los datos recibidos, se proporciona un búfer como primer parámetro. En el ejemplo anterior, el tipo de búfer es boost :: array, que proviene de la biblioteca Boost C ++ Array y se define en boost / array.hpp.
 
 Siempre que se reciban y guarden uno o más bytes en el búfer, la función read_handler () ser llamado. El número exacto de bytes viene dado por el parámetro bytes_transferred de tipo std :: size_t. Según la misma regla, el identificador debe mirar primero el parámetro ec para verificar si hay errores de recepción. Si se recibe correctamente, los datos se escriben en el flujo de salida estándar.
 
 Tenga en cuenta que read_handler () llamará al método async_read_some () nuevamente después de escribir los datos en std :: cout. Esto es necesario porque no hay garantía de que la página web completa pueda recibirse en una sola operación asincrónica. Las llamadas alternativas de async_read_some () y read_handler () solo se terminan cuando la conexión se interrumpe, como cuando el servidor web ha entregado una página web completa. En este caso, se informará un error dentro de read_handler () para evitar una mayor salida de datos al flujo de salida estándar y más llamadas al método async_read () en el socket. En este punto, la rutina se detendrá porque no hay más operaciones asincrónicas.
 
 El ejemplo anterior se utiliza para recuperar la página web de www.highscore.de, mientras que el siguiente ejemplo muestra un servidor web simple. La principal diferencia es que esta aplicación no se conecta a otras PC, sino que espera a conectarse.

 #include <boost/asio.hpp> 
 #include <string> 
 
 boost::asio::io_service io_service; 
 boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 80); 
 boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint); 
 boost::asio::ip::tcp::socket sock(io_service); 
 std::string data = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!"; 
 
 void write_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
 { 
 } 
 
 void accept_handler(const boost::system::error_code &ec) 
 { 
   if (!ec) 
   { 
     boost::asio::async_write(sock, boost::asio::buffer(data), write_handler); 
   } 
 } 
 
 int main() 
 { 
   acceptor.listen(); 
   acceptor.async_accept(sock, accept_handler); 
   io_service.run(); 
 } 

El aceptador de objetos de E / S de tipo boost :: asio :: ip :: tcp :: accepttor -inicializado al protocolo y número de puerto especificados- se usa para esperar conexiones entrantes desde otras PC. La inicialización se realiza a través del objeto endpoint. El tipo de objeto es boost :: asio :: ip :: tcp :: endpoint. El receptor en este ejemplo está configurado para usar el puerto 80 para esperar la conexión entrante de IP v4. Es el puerto y protocolo comúnmente utilizado por WWW.
 
 Una vez que se inicializa el receptor, main () primero llama al método listen () para poner el receptor en el estado de recepción, y luego usa el método async_accept () para esperar la conexión inicial. El conector utilizado para enviar y recibir datos se pasa como primer parámetro.
 
 Cuando una PC intenta establecer una conexión, accept_handler () se llama automáticamente. Si la solicitud de conexión es exitosa, se ejecuta la función gratuita boost :: asio :: async_write () para enviar la información almacenada en los datos a través del socket. boost :: asio :: ip :: tcp :: socket tiene un método llamado async_write_some () que también puede enviar datos; pero llamará al identificador asociado después de enviar al menos un byte. El identificador necesita calcular cuántos bytes quedan y llamar a async_write_some () repetidamente hasta que se envíen todos los bytes. El uso de boost :: asio :: async_write () puede evitar esto, porque esta operación asincrónica solo termina después de que se hayan enviado todos los bytes en el búfer.
 
 En este ejemplo, cuando se envían todos los datos, se llamará a la función vacía write_handler (). Dado que todas las operaciones asincrónicas se han completado, la aplicación termina. Las conexiones con otras PC también se cierran en consecuencia.
 7.5. Desarrollar la extensión Boost.Asio
 
 Aunque Boost.Asio admite principalmente funciones de red, es muy fácil agregar otros objetos de E / S para realizar otras operaciones asincrónicas. Esta sección presentará un diseño general de la extensión Boost.Asio. Aunque esto no es necesario, proporciona un marco viable para otras extensiones como punto de partida.
 
 Para agregar nuevas operaciones asincrónicas a Boost.Asio, debe implementar las siguientes tres clases:
 
     una clase derivada de boost :: asio :: basic_io_object para representar nuevos objetos de E / S. Los desarrolladores que utilicen esta nueva extensión de Boost.Asio solo verán este objeto de E / S.
 
     Una clase derivada de boost :: asio :: io_service :: service representa un servicio que está registrado como un servicio de E / S y se puede acceder desde el objeto de E / S. La distinción entre servicios y objetos de E / S es importante porque en un momento dado, cada servicio de E / S solo puede tener una instancia de servicio, y varios objetos de E / S pueden acceder a un servicio.
 
     Una clase que no deriva de ninguna otra clase representa la realización concreta del servicio. Dado que cada servicio de E / S solo puede tener una instancia de servicio en un momento dado, el servicio creará una instancia de su implementación específica para cada objeto de E / S. Esta instancia gestiona datos internos relacionados con el objeto de E / S correspondiente.
 
 La extensión Boost.Asio desarrollada en esta sección no solo proporciona un marco, sino que simula un objeto boost :: asio :: deadline_timer disponible. La diferencia entre este y el boost :: asio :: deadline_timer original es que la duración del temporizador se pasa como un parámetro al método wait () o async_wait () en lugar de pasar al constructor.

#include <boost/asio.hpp> 
 #include <cstddef> 
 
 template <typename Service> 
 class basic_timer 
   : public boost::asio::basic_io_object<Service> 
 { 
   public: 
     explicit basic_timer(boost::asio::io_service &io_service) 
       : boost::asio::basic_io_object<Service>(io_service) 
     { 
     } 
 
     void wait(std::size_t seconds) 
     { 
       return this->service.wait(this->implementation, seconds); 
     } 
 
     template <typename Handler> 
     void async_wait(std::size_t seconds, Handler handler) 
     { 
       this->service.async_wait(this->implementation, seconds, handler); 
     } 
 }; 


Cada objeto de E / S generalmente se implementa como una clase de plantilla que requiere la instanciación de un servicio, generalmente el servicio desarrollado específicamente para este objeto de E / S. Cuando se crea una instancia de un objeto de E / S, el servicio se registrará automáticamente como un servicio de E / S a través de la clase principal boost :: asio :: basic_io_object a menos que se haya registrado antes. Esto garantiza que los servicios utilizados por cualquier objeto de E / S solo se registren una vez por servicio de E / S.
 
 Dentro del objeto I / O se puede acceder al servicio correspondiente a través de la referencia de servicio El acceso habitual es desviar la llamada al método al servicio. Dado que el servicio necesita almacenar datos para cada objeto de E / S, se crea automáticamente una instancia para cada objeto de E / S que utiliza el servicio. Esto se logra con la ayuda de la clase padre boost :: asio :: basic_io_object. La implementación del servicio real se pasa como parámetro a cualquier llamada de método, de modo que el servicio pueda saber qué objeto de E / S inició la llamada. Se accede a la implementación específica del servicio a través del atributo de implementación.
 
 En términos generales, los objetos de E / S son relativamente simples: la instalación de servicios y la creación de implementaciones de servicios se realizan mediante la clase principal boost :: asio :: basic_io_object, y las llamadas a métodos simplemente se reenvían al servicio correspondiente; el servicio real La implementación del objeto de E / S se puede utilizar como parámetro.

#include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <boost/bind.hpp> 
 #include <boost/scoped_ptr.hpp> 
 #include <boost/shared_ptr.hpp> 
 #include <boost/weak_ptr.hpp> 
 #include <boost/system/error_code.hpp> 
 
 template <typename TimerImplementation = timer_impl> 
 class basic_timer_service 
   : public boost::asio::io_service::service 
 { 
   public: 
     static boost::asio::io_service::id id; 
 
     explicit basic_timer_service(boost::asio::io_service &io_service) 
       : boost::asio::io_service::service(io_service), 
       async_work_(new boost::asio::io_service::work(async_io_service_)), 
       async_thread_(boost::bind(&boost::asio::io_service::run, &async_io_service_)) 
     { 
     } 
 
     ~basic_timer_service() 
     { 
       async_work_.reset(); 
       async_io_service_.stop(); 
       async_thread_.join(); 
     } 
 
     typedef boost::shared_ptr<TimerImplementation> implementation_type; 
 
     void construct(implementation_type &impl) 
     { 
       impl.reset(new TimerImplementation()); 
     } 
 
     void destroy(implementation_type &impl) 
     { 
       impl->destroy(); 
       impl.reset(); 
     } 
 
     void wait(implementation_type &impl, std::size_t seconds) 
     { 
       boost::system::error_code ec; 
       impl->wait(seconds, ec); 
       boost::asio::detail::throw_error(ec); 
     } 
 
     template <typename Handler> 
     class wait_operation 
     { 
       public: 
         wait_operation(implementation_type &impl, boost::asio::io_service &io_service, std::size_t seconds, Handler handler) 
           : impl_(impl), 
           io_service_(io_service), 
           work_(io_service), 
           seconds_(seconds), 
           handler_(handler) 
         { 
         } 
 
         void operator()() const 
         { 
           implementation_type impl = impl_.lock(); 
           if (impl) 
           { 
               boost::system::error_code ec; 
               impl->wait(seconds_, ec); 
               this->io_service_.post(boost::asio::detail::bind_handler(handler_, ec)); 
           } 
           else 
           { 
               this->io_service_.post(boost::asio::detail::bind_handler(handler_, boost::asio::error::operation_aborted)); 
           } 
       } 
 
       private: 
         boost::weak_ptr<TimerImplementation> impl_; 
         boost::asio::io_service &io_service_; 
         boost::asio::io_service::work work_; 
         std::size_t seconds_; 
         Handler handler_; 
     }; 
 
     template <typename Handler> 
     void async_wait(implementation_type &impl, std::size_t seconds, Handler handler) 
     { 
       this->async_io_service_.post(wait_operation<Handler>(impl, this->get_io_service(), seconds, handler)); 
     } 
 
   private: 
     void shutdown_service() 
     { 
     } 
 
     boost::asio::io_service async_io_service_; 
     boost::scoped_ptr<boost::asio::io_service::work> async_work_; 
     boost::thread async_thread_; 
 }; 
 
 template <typename TimerImplementation> 
 boost::asio::io_service::id basic_timer_service<TimerImplementation>::id; 


     Para integrarse con Boost.Asio, un servicio debe cumplir varios requisitos:
 
     debe derivarse de boost :: asio :: io_service :: service. El constructor debe aceptar una referencia a un servicio de E / S, que se pasará al constructor de boost :: asio :: io_service :: service en consecuencia.
 
     Cualquier servicio debe contener un ID de atributo público estático de tipo boost :: asio :: io_service :: id. Este atributo se utiliza para identificar el servicio dentro del servicio de E / S.
 
     Se deben definir dos métodos públicos llamados construct () y destruct (), y ambos requieren un parámetro de tipo deployment_type. deployment_type suele ser la definición de tipo de la implementación específica del servicio. Como muestra el ejemplo anterior, es fácil usar un objeto boost :: shared_ptr en construct () para inicializar una implementación de servicio y destruirla en consecuencia en destruct (). Dado que estos dos métodos se llaman automáticamente cuando se crea o destruye un objeto de E / S, se puede implementar un servicio usando construct () y destruct () para crear y destruir servicios para cada objeto de E / S.
 
     Se debe definir un método llamado shutdown_service (); sin embargo, puede ser privado. Para las extensiones generales de Boost.Asio, suele ser un método vacío. Solo los servicios que están muy estrechamente integrados con Boost.Asio lo usarán. Pero este método debe estar presente para que la extensión se compile correctamente.
 
 Para reenviar la llamada al método al servicio correspondiente, el método a reenviar debe estar definido para el objeto de E / S correspondiente. Estos métodos suelen tener nombres similares a los métodos del objeto de E / S, como wait () y async_wait () en el ejemplo anterior. Los métodos síncronos, como wait (), simplemente acceden a la implementación específica del servicio para llamar a un método de bloqueo, mientras que los métodos asíncronos, como async_wait (), llaman al método de bloqueo en un hilo.
 
 El uso de operaciones asincrónicas con la ayuda de subprocesos generalmente se logra accediendo a un nuevo servicio de E / S. El ejemplo anterior contiene un atributo llamado async_io_service_ cuyo tipo es boost :: asio :: io_service. El método run () de este servicio de E / S se inicia en su propio hilo, y su hilo es creado por async_thread_ de tipo boost :: thread dentro del constructor del servicio. El tipo del tercer atributo async_work_ es boost :: scoped_ptr <boost :: asio :: io_service :: work>, que se usa para evitar que el método run () regrese inmediatamente. De lo contrario, esto puede suceder porque no se están creando otras operaciones asincrónicas. Cree un objeto de tipo boost :: asio :: io_service :: work y vincúlelo al servicio de E / S. Esta acción también ocurre en el constructor del servicio, evitando que el método run () regrese inmediatamente.
 
 Un servicio también se puede implementar sin acceder a su propio servicio de E / S; un solo hilo es suficiente. La razón para usar un nuevo servicio de E / S para los subprocesos recién agregados es que es más simple: los servicios de E / S se pueden usar entre subprocesos para comunicarse entre sí con mucha facilidad. En este ejemplo, async_wait () crea un objeto de función de tipo wait_operation y lo pasa al servicio de E / S interno a través del método post (). Luego, en el hilo utilizado para ejecutar el método run () de este servicio de E / S interno, llame al operador sobrecargado () () del objeto de función. post () proporciona una forma sencilla de ejecutar un objeto de función en otro hilo.
 
 El operador sobrecargado operator () () de wait_operation básicamente realiza el mismo trabajo que el método wait (): llamar al método de bloqueo wait () en la implementación del servicio. Sin embargo, es posible que el objeto de E / S y su implementación de servicio se destruyan durante la ejecución del operador operator () () en este hilo. Si la implementación del servicio se destruye en destruct (), el operador operator () () ya no podrá acceder a ella. Esta situación se evita mediante el uso de un puntero débil Del primer capítulo sabemos: si la implementación del servicio todavía existe cuando se llama a lock (), el puntero débil impl_ devuelve uno de sus punteros compartidos, de lo contrario devolverá 0. En este caso, operator () () no accede a la implementación del servicio, sino que llama al identificador con un error boost :: asio :: error :: operation_aborted.

 #include <boost/system/error_code.hpp> 
 #include <cstddef> 
 #include <windows.h> 
 
 class timer_impl 
 { 
   public: 
     timer_impl() 
       : handle_(CreateEvent(NULL, FALSE, FALSE, NULL)) 
     { 
     } 
 
     ~timer_impl() 
     { 
       CloseHandle(handle_); 
     } 
 
     void destroy() 
     { 
       SetEvent(handle_); 
     } 
 
     void wait(std::size_t seconds, boost::system::error_code &ec) 
     { 
       DWORD res = WaitForSingleObject(handle_, seconds * 1000); 
       if (res == WAIT_OBJECT_0) 
         ec = boost::asio::error::operation_aborted; 
       else 
         ec = boost::system::error_code(); 
     } 
 
 private: 
     HANDLE handle_; 
 }; 


La implementación del servicio timer_impl usa funciones de la API de Windows, que solo se pueden compilar y usar en Windows. El propósito de este ejemplo es solo ilustrar una realización potencial.
 
 timer_impl proporciona dos métodos básicos: wait () se usa para esperar unos segundos. destroy () se usa para cancelar una operación de espera, que es necesaria porque para las operaciones asincrónicas, el método wait () se llama en su propio hilo. Si el objeto de E / S y su implementación de servicio se destruyen, el método de bloqueo wait () debe usar destroy () para cancelarlo.
 
 Esta extensión de Boost.Asio se puede utilizar de la siguiente manera.

#include <boost/asio.hpp> 
 #include <iostream> 
 #include "basic_timer.hpp" 
 #include "timer_impl.hpp" 
 #include "basic_timer_service.hpp" 
 
 void wait_handler(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 typedef basic_timer<basic_timer_service<> > timer; 
 
 int main() 
 { 
   boost::asio::io_service io_service; 
   timer t(io_service); 
   t.async_wait(5, wait_handler); 
   io_service.run(); 
 } 

En comparación con el ejemplo al principio de este capítulo, el uso de esta extensión de Boost.Asio es similar a boost :: asio :: deadline_timer. En la práctica, boost :: asio :: deadline_timer debe usarse primero, porque ya está integrado en Boost.Asio. El único propósito de esta extensión es demostrar cómo Boost.Asio extiende nuevas operaciones asincrónicas.
 
 Directory Monitor es una extensión de Boost.Asio en realidad, proporciona un objeto de E / S que puede monitorear directorios. Si se crea, modifica o elimina un archivo en el directorio supervisado, se llamará a un identificador en consecuencia. La versión actual es compatible con Windows y Linux (versión del kernel 2.6.13 o superior).

 

Punto final del puerto

que es端口endpoint

Al realizar una comunicación de red, debe conocer tres elementos: dirección IP, protocolo de comunicación y número de puerto. El protocolo de comunicación se utiliza para determinar cómo comunicarse, y la dirección IP y el número de puerto se utilizan para determinar el objetivo. El Boost.Asiomodelo correspondiente se proporciona en él para indicarlo Estos tres elementos son el puerto: ip::basic_endpointcontiene la dirección IP y el número de puerto, y utiliza el tipo de protocolo de comunicación como parámetro de plantilla. Los que se pueden utilizar directamente son:

ip::tcp::endpoint
ip::udp::endpoint
ip::icmp::endpoint

Lo que necesitas saber

La construcción del puerto local
se puede construir especificando el protocolo y el número de puerto. Suele utilizarse para recibir nuevas conexiones, por ejemplo:
tcp::endpoint local_ep(ip::tcp::v4(),1024);

La construcción del puerto remoto El
puerto se puede construir directamente para la comunicación cuando se conocen la dirección IP y el número de puerto del host remoto, por ejemplo:
tcp::endpoint remote_ep(ip::address::from_string("127.0.0.1"),1024);

Cómo obtener el nombre de host y los nombres del servicio de puerto
deben utilizar el servicio DNS ha sido la dirección IP correspondiente del host en el Boost.Asioproporcionado ip::tcp::resolver, etc. para obtener el puerto

para resumir

El puerto es equivalente a una dirección específica 地址y la comunicación se realiza en base a esto .

Supongo que te gusta

Origin blog.csdn.net/sunlin972913894/article/details/103483317
Recomendado
Clasificación