[Impulsar la biblioteca de red de bronce a rey] Parte 7: servidor de eco asíncrono en la programación de red asio, centrándose en la respuesta

1. Introducción

La API de operación asincrónica se introdujo antes . Hoy escribiré un servidor de eco asincrónico simple, centrándome en las respuestas.

2. El modo eco responde al servidor asíncrono

2.1 Clase de sesión

La clase de sesión es principalmente una clase de sesión que maneja la recepción y el envío de mensajes del cliente. Por simplicidad, no consideramos el problema de los paquetes adhesivos, ni consideramos la interfaz que admite llamadas manuales para enviar. Solo enviamos y recibir una longitud fija (1024 bytes de longitud) en forma de respuestas . ) datos.

“sesión.h” :

#pragma once
#include<iostream>
#include<string>
#include<boost/asio.hpp>

class Session
{
    
    
public:
	Session(boost::asio::io_context& ioc);
public:
	boost::asio::ip::tcp::socket& GetSocket();
	void Start();

protected:
	//接收数据回调 tcp接收缓冲区有数据
	void HandlerRead(const boost::system::error_code& err, size_t bytes_transferred);
	//发送数据回调 tcp发送缓冲区有空闲空间,就会从用户缓冲区拷贝到tcp发送缓冲区,然后发送数据
	void HandleSend(const boost::system::error_code& err);
protected:
	enum {
    
    
		max_length = 1024
	};
	//数组接收数据
	char data_[max_length];

private:
	boost::asio::ip::tcp::socket socket_;
};

Este código es una definición de clase C++ implementada utilizando la biblioteca Boost.Asio para sesiones de comunicación de red. Interpretas este código:

  • El archivo de encabezado contiene:
    • Estas líneas incluyen los archivos de encabezado necesarios. #pragma once es una directiva de preprocesamiento que garantiza que los archivos de encabezado incluidos solo se incluirán una vez, evitando inclusiones múltiples y problemas potenciales.
#pragma once
#include<iostream>
#include<string>
#include<boost/asio.hpp>
  • Declaración de clase:
    • El código define una clase C++ llamada Session . Tiene un constructor que acepta una referencia a un objeto boost::asio::io_context y tiene dos funciones miembro públicas: GetSocket y Start .
class Session
{
    
    
public:
    Session(boost::asio::io_context& ioc);
public:
    boost::asio::ip::tcp::socket& GetSocket();
    void Start();
protected:
    // ...
private:
    // ...
};

  • Funciones de los miembros públicos:

    • Session(boost::asio::io_context& ioc): Este es el constructor de la clase Session . Acepta una referencia a io_context como parámetro y generalmente se usa para administrar operaciones de E/S asincrónicas .

    • boost::asio::ip::tcp::socket& GetSocket() : esta función devuelve una referencia a un objeto de socket TCP . Puede permitir que un código externo acceda al socket asociado con esta sesión.

    • void Start(): se espera que esta función inicie la sesión. El fragmento de código no proporciona su implementación real, pero puede iniciar algunas operaciones asincrónicas para la comunicación de red.

  • Funciones de miembros protegidas:

    • Estas son funciones de miembros protegidas. Pueden ser anulados por clases derivadas o utilizados internamente para manejar operaciones de lectura y envío.

      • HandlerRead(const boost::system::error_code& err, size_t bytes_transferred): esta función parece ser una devolución de llamada para manejar los datos recibidos a través de una conexión TCP. Acepta un código de error y el número de bytes transferidos como parámetros.

      • HandleSend(const boost::system::error_code& err): esta función parece ser una devolución de llamada utilizada para gestionar la finalización del envío de datos. También acepta un código de error como parámetro.

protected:
    void HandlerRead(const boost::system::error_code& err, size_t bytes_transferred);
    void HandleSend(const boost::system::error_code& err);

  • Variables de miembros privados:
private:
    boost::asio::ip::tcp::socket socket_;

Esta variable miembro privada socket_ es una instancia de un socket TCP y se utiliza para la comunicación de red dentro de la sesión.

  • constante:
enum {
    
    
    max_length = 1024
};

Esto define una constante llamada max_length con un valor de 1024 . Lo más probable es que esta sea la longitud máxima del búfer utilizado para recibir datos.

En general, este código es el esquema básico de una clase de sesión de red que utiliza la biblioteca Boost.Asio . Proporciona la estructura y los componentes necesarios para gestionar las comunicaciones de red, pero no proporciona la implementación real del comportamiento de la sesión en este fragmento.

“sesión.cpp” :

#include "Session.h"

Session::Session(boost::asio::io_context& io_context)
	:socket_(io_context)
{
    
    
	memset(data_, 0, sizeof(data_));
}

boost::asio::ip::tcp::socket& Session::GetSocket() {
    
    
	return socket_;
}

void Session::Start() {
    
    
	memset(data_, 0, max_length);
	socket_.async_read_some(boost::asio::buffer(data_, max_length),
		std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
}

void Session::HandlerRead(const boost::system::error_code& err, std::size_t bytes_transferred) {
    
    
	if (0 != err.value()) {
    
    
		std::cout << "read data failed!err_code is: " << err.value() << " .message: " << err.what() << std::endl;
		delete this;
	}else {
    
    
		std::cout << "receive data is: " << data_ << std::endl;

		//大部分服务器这样设计全双工通信
		memset(data_, 0, sizeof(data_));
		//继续让接收/读数据监听,这样就会造成删除bug
		socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_))
			, std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));

		socket_.async_send(boost::asio::buffer(data_, max_length),
			std::bind(&Session::HandleSend, this, std::placeholders::_1));
	}
}

void Session::HandleSend(const boost::system::error_code& err) {
    
    
	if (0 != err.value()) {
    
    
		std::cout << "send data failed!err code is: " << err.value() << " .message: " << err.what() << std::endl;
	}
	else {
    
    
		memset(data_, 0, sizeof(data_));
		//继续让接收/读数据监听
		socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_))
			, std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
	}

}

Este es el archivo de implementación relacionado con la clase Session proporcionada anteriormente . Explique este código:

  • Constructor:
    • Esta es la implementación del constructor de la clase Session . Acepta una referencia a boost::asio::io_context como parámetro e inicializa socket_ con ese contexto . Además, utiliza la función memset para inicializar el contenido de data_array a cero.
Session::Session(boost::asio::io_context& io_context)
    : socket_(io_context)
{
    
    
    memset(data_, 0, sizeof(data_));
}

  • Función GetSocket :
    • Esta función devuelve una referencia a socket_ , lo que permite que el código externo acceda al socket TCP asociado con la sesión .
boost::asio::ip::tcp::socket& Session::GetSocket() {
    
    
    return socket_;
}

  • Función de inicio , en el método de inicio llamamos a una operación de lectura asincrónica y escuchamos los mensajes enviados por el par. Cuando el par envía datos, se activa la función de devolución de llamada HandlerRead:
    • La función Inicio parece ser la función que inicia la sesión. Primero usa memset para inicializar el contenido de la matriz data_ a cero y luego llama a la función async_read_some de socket_ para iniciar una operación de lectura asincrónica. Cuando llegan los datos, se llama a la función de devolución de llamada HandlerRead para procesar los datos leídos.
void Session::Start() {
    
    
    memset(data_, 0, max_length);
    socket_.async_read_some(boost::asio::buffer(data_, max_length),
        std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
}

  • Función HandlerRead , la función HandlerRead envía los datos recibidos al par y, cuando se completa el envío, se activa la función de devolución de llamada HandleSend :
    • La función HandlerRead es una devolución de llamada que se utiliza para controlar la finalización de una operación de lectura asincrónica. Comprueba si ocurrió un error y, de ser así, imprime un mensaje de error y elimina el objeto de sesión. Si no hay errores, genera los datos recibidos y continúa escuchando para recibir/leer datos llamando a async_read_some nuevamente. Al mismo tiempo, también inicia una operación de envío asincrónica para enviar datos al cliente.
void Session::HandlerRead(const boost::system::error_code& err, std::size_t bytes_transferred) {
    
    
    if (0 != err.value()) {
    
    
        std::cout << "read data failed! err_code is: " << err.value() << " . message: " << err.what() << std::endl;
        delete this;
    }
    else {
    
    
        std::cout << "receive data is: " << data_ << std::endl;

        // 大部分服务器这样设计全双工通信
        memset(data_, 0, sizeof(data_));
        // 继续让接收/读数据监听, 这样就会造成删除bug
        socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_)),
            std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));

        socket_.async_send(boost::asio::buffer(data_, max_length),
            std::bind(&Session::HandleSend, this, std::placeholders::_1));
    }
}

  • Función HandleSend :
    • La función HandleSend se utiliza para manejar la devolución de llamada cuando se completa la operación de envío asincrónico. Comprueba si ocurrió un error y, de ser así, imprime un mensaje de error y elimina el objeto de sesión. Si no hay errores, continúa escuchando la recepción/lectura de datos llamando a async_read_some nuevamente . El evento de lectura se monitorea nuevamente en la función HandleSend . Si el par tiene datos enviados, se activa HandlerRead y luego devolvemos los datos recibidos. Para lograr el efecto de un servicio receptivo.
void Session::HandleSend(const boost::system::error_code& err) {
    
    
    if (0 != err.value()) {
    
    
        std::cout << "send data failed! err code is: " << err.value() << " . message: " << err.what() << std::endl;
        delete this;
    }
    else {
    
    
        memset(data_, 0, sizeof(data_));
        // 继续让接收/读数据监听
        socket_.async_read_some(boost::asio::buffer(data_, sizeof(data_)),
            std::bind(&Session::HandlerRead, this, std::placeholders::_1, std::placeholders::_2));
    }
}

En general, este código es la implementación de la clase Session , que maneja operaciones asincrónicas de lectura y envío de datos, y proporciona cierta lógica de manejo de errores y limpieza de datos. Esta es una implementación de una clase de sesión de red básica, utilizada para manejar la comunicación de red.

2.2 La clase Servidor es la clase de gestión para que el servidor reciba conexiones.

La clase Servidor es la clase de administración para que el servidor reciba conexiones.

"servidor.h":

#pragma once
#include<iostream>
#include<boost/asio.hpp>
#include"Session.h"

class Server
{
    
    
public:
	Server(boost::asio::io_context& io_context, int16_t port);
private:
	void StartAccept();
	void HandleAccept(Session* session, const boost::system::error_code& err);
private:
	boost::asio::io_context& io_context_;
	boost::asio::ip::tcp::acceptor acceptor_;
};

Este código define una clase C++ llamada Servidor, que parece usarse para crear y administrar un servidor TCP basado en la biblioteca Boost.Asio . Aquí hay una explicación de este código:

  • El archivo de encabezado contiene:
    • Estas líneas incluyen los archivos de encabezado necesarios. #pragma once se utiliza para garantizar que el archivo de encabezado solo se incluya una vez para evitar el problema de inclusiones múltiples.
#pragma once
#include <iostream>
#include <boost/asio.hpp>
#include "Session.h"
  • Declaración de clase:
    • Este código define una clase **C++** denominada Servidor , que tiene un constructor público, dos funciones miembro privadas y dos variables miembro privadas.
class Server
{
    
    
public:
    Server(boost::asio::io_context& io_context, int16_t port);
private:
    void StartAccept();
    void HandleAccept(Session* session, const boost::system::error_code& err);
private:
    boost::asio::io_context& io_context_;
    boost::asio::ip::tcp::acceptor acceptor_;
};

  • Constructor:
    • Esta es la implementación del constructor de la clase Servidor . El constructor acepta una referencia a un objeto boost::asio::io_context y un número de puerto como parámetros. Dentro del constructor, inicializa las variables miembro io_context_ y Acceptor_. io_context_ se usa para administrar operaciones asincrónicas, mientras que Acceptor_ se usa para escuchar conexiones entrantes. El constructor también llama a la función StartAccept para comenzar a aceptar conexiones.
Server::Server(boost::asio::io_context& io_context, int16_t port)
    : io_context_(io_context), acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
    
    
    StartAccept();
}

  • Función de miembro privado: StartAccept vincula al aceptador para recibir la conexión al servicio. Internamente, vincula el descriptor de socket correspondiente al accpeptor al modelo epoll o iocp para lograr la unidad de eventos. HandleAccept es la función de devolución de llamada que se activa cuando llega una nueva conexión.
    • void StartAccept (): la función StartAccept crea un nuevo objeto de sesión y luego usa la función async_accept de Acceptor_ para esperar y aceptar asincrónicamente la conexión entrante. Cuando se acepta la conexión, se llama a la función HandleAccept para manejar la conexión.
void Server::StartAccept() {
    
    
    Session* new_session = new Session(io_context_);
    acceptor_.async_accept(new_session->GetSocket(),
        std::bind(&Server::HandleAccept, this, new_session, std::placeholders::_1));
}

  • void HandleAccept( Sesión de sesión, const boost::system::error_code& err): *
    • La función HandleAccept se utiliza para manejar devoluciones de llamada para aceptar operaciones de conexión. Si no se producen errores, llama al método Start del objeto Session para iniciar la sesión. Si ocurre un error, elimina el objeto Sesión . Luego, pase lo que pase, continúa llamando a la función StartAccept para esperar la siguiente conexión.
void Server::HandleAccept(Session* session, const boost::system::error_code& err) {
    
    
    if (!err) {
    
    
        session->Start();
    } else {
    
    
        delete session;
    }
    StartAccept(); // 继续等待下一个连接
}

En resumen, este código define una clase de servidor TCP Server basado en Boost.Asio , que inicializa las variables miembro necesarias en el constructor y comienza a aceptar conexiones entrantes. Utiliza operaciones asincrónicas para manejar solicitudes de conexión e inicia una nueva sesión ( Session ) después de que se acepta cada conexión.

"servidor.cpp":

#include"server.h"

Server::Server(boost::asio::io_context& io_context,int16_t port)
	:io_context_(io_context)
	,acceptor_(io_context,boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(),port))
{
    
    
	StartAccept();
}

void Server::StartAccept() {
    
    
	Session* new_session = new Session(io_context_);
	acceptor_.async_accept(new_session->GetSocket(),
		std::bind(&Server::HandleAccept, this, new_session, std::placeholders::_1));
}

void Server::HandleAccept(Session* new_session, const boost::system::error_code& err)
{
    
    
	if (err.value() != 0)
	{
    
    
		std::cout << "acceptor session failed.error_code is: " << err.value() << " .message: " << err.what() << std::endl;
		delete new_session;
	}
	else {
    
    
		std::cout << "accept new session success!" << std::endl;
		std::cout << "client connect,the ip:" << new_session->GetSocket().remote_endpoint().address() << std::endl;

		new_session->Start();
	}
	//继续监听新的客户端连接
	StartAccept();
}

Este código es el archivo C++ server.cpp , que implementa una función miembro de la clase de servidor TCP Server basada en la biblioteca Boost.Asio . Aquí hay una explicación detallada del código:

  • Constructor:
    • Esta es la implementación del constructor de la clase Servidor . El constructor acepta una referencia a un objeto boost::asio::io_context y un número de puerto como parámetros. Dentro del constructor, inicializa las variables miembro io_context_ y Acceptor_ . io_context_ se usa para administrar operaciones asincrónicas, mientras que Acceptor_ se usa para escuchar conexiones entrantes. El constructor también llama a la función StartAccept para comenzar a aceptar conexiones.
Server::Server(boost::asio::io_context& io_context, int16_t port)
    : io_context_(io_context)
    , acceptor_(io_context, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
    
    
    StartAccept();
}

  • Función Iniciar aceptación :
    • La función StartAccept crea un nuevo objeto Session y luego usa la función async_accept de Acceptor_ para esperar y aceptar conexiones entrantes de forma asincrónica. Cuando se acepta la conexión, se llama a la función HandleAccept para manejar la conexión.
void Server::StartAccept() {
    
    
    Session* new_session = new Session(io_context_);
    acceptor_.async_accept(new_session->GetSocket(),
        std::bind(&Server::HandleAccept, this, new_session, std::placeholders::_1));
}

  • Función HandleAccept :
    • La función HandleAccept es una devolución de llamada que se utiliza para manejar la aceptación de operaciones de conexión. Si no se produce ningún error (err.value() == 0), imprime un mensaje de conexión exitosa y muestra la dirección IP del cliente. Luego, llama al método Start del objeto Session para iniciar la sesión. Si ocurre un error, imprime un mensaje de error y elimina el objeto Sesión . De todos modos, al final seguirá llamando a la función StartAccept para esperar la siguiente conexión.
void Server::HandleAccept(Session* new_session, const boost::system::error_code& err)
{
    
    
    if (err.value() != 0)
    {
    
    
        std::cout << "acceptor session failed. error_code is: " << err.value() << " . message: " << err.what() << std::endl;
        delete new_session;
    }
    else {
    
    
        std::cout << "accept new session success!" << std::endl;
        std::cout << "client connect, the IP: " << new_session->GetSocket().remote_endpoint().address() << std::endl;

        new_session->Start();
    }
    // 继续监听新的客户端连接
    StartAccept();
}

En general, este código es la implementación de la función miembro de la clase Servidor, que se utiliza para aceptar la solicitud de conexión del cliente e iniciar la sesión después de aceptar la conexión. Esta es la parte de un servidor TCP básico que maneja las solicitudes de conexión entrantes.

“principal.cpp”:

#include"server.h"

int main() {
    
    
	try {
    
    
		boost::asio::io_context io_context;
		Server server(io_context, 9273);
		io_context.run();
	}
	catch (std::exception& e) {
    
    
		std::cout << "exception: " << e.what() << std::endl;
	}
	return 0;
}

Este código es la función principal de un programa C++ , que crea un servidor TCP basado en la biblioteca Boost.Asio y lo ejecuta. Aquí hay una explicación detallada del código:

  • El archivo de encabezado contiene:
    • Esta línea de código contiene un archivo de encabezado llamado "server.h" , que debe contener la declaración de la clase Servidor y otros archivos de encabezado necesarios.
#include "server.h"

  • función principal:
    • boost::asio::io_context io_context; : Crea un objeto io_context , que se utiliza para gestionar operaciones asincrónicas. La biblioteca Boost.Asio generalmente requiere un objeto io_context para coordinar y administrar operaciones asincrónicas.

    • Servidor servidor(io_context, 9273); crea un servidor de objetos de la clase Servidor y pasa el objeto io_context y un número de puerto ( 9273 en este ejemplo ) como parámetros. Al hacerlo, se iniciará el servidor y comenzará a escuchar en el puerto especificado.

    • i o_context.run(); Llame al método de ejecución del objeto io_context para comenzar a ejecutar el bucle de eventos, que continuará ejecutándose hasta que no haya operaciones asincrónicas pendientes. Aquí, se ejecutará todo el tiempo para escuchar y manejar las solicitudes de conexión del cliente.

    • catch (std::exception& e) {…}: este es un bloque de manejo de excepciones que se utiliza para detectar cualquier excepción que pueda generarse. Si ocurre una excepción, imprimirá una descripción de la excepción.

int main() {
    
    
    try {
    
    
        boost::asio::io_context io_context;
        Server server(io_context, 9273);
        io_context.run();
    }
    catch (std::exception& e) {
    
    
        std::cout << "exception: " << e.what() << std::endl;
    }
    return 0;
}

En resumen, esta función principal crea un servidor TCP , inicia el servidor llamando a io_context.run() e ingresa al bucle de eventos, esperando las solicitudes de conexión del cliente. Si se produce una excepción, la detecta e imprime un mensaje de error. Este es un punto de entrada al servidor simple para iniciar una aplicación de servidor.

3. Cliente

El diseño del cliente puede utilizar el modo sincrónico anterior. El cliente no necesita un método asincrónico porque el cliente no es principalmente concurrente. Por supuesto, es mejor escribir recepción y recepción asincrónica. El código no se mostrará aquí, si estás interesado consulta el artículo anterior.

Después de ejecutar el servidor, ejecute el cliente. Después de ingresar la cadena, puede recibir la cadena respondida por el servidor.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
modo de respuesta de eco:
Insertar descripción de la imagen aquí

4. Peligros ocultos

Este ejemplo de demostración está escrito después del sitio web oficial de asio . Hay un peligro oculto. Cuando el servidor está a punto de enviar datos (antes de llamar a async_write ), el cliente se interrumpe en este momento. Cuando el servidor llama a async_write en este momento , lo hará activa la función de devolución de llamada. Juzgará que ec no es 0 y luego lo ejecutará. Eliminar esto recicla lógicamente la sesión. Pero debe tenerse en cuenta que una vez que se cierra el cliente, el evento de lectura lista se activará en el nivel tcp y el servidor activará la función de devolución de llamada del evento de lectura. En la función de devolución de llamada del evento de lectura, se determina que el código de error ec no es 0 y luego se realiza nuevamente la operación de eliminación , lo que resulta en una destrucción secundaria, lo cual es extremadamente peligroso.
Insertar descripción de la imagen aquí

5. Resumen

Este artículo presenta el diseño de un servidor de respuesta asíncrono, pero este tipo de servidor no se utilizará en la producción real, principalmente por dos razones:

  • Debido a que el envío y la recepción del servidor interactúan en forma de respuestas, no puede lograr el propósito del envío aleatorio por parte de la capa de aplicación, es decir, no logra una separación completa entre el envío y la recepción (lógica full-duplex).
  • El servidor no maneja problemas como paquetes fijos, serialización y desacoplamiento de lógica y envío y recepción de subprocesos.
  • El servidor corre el riesgo de sufrir una destrucción secundaria.

Supongo que te gusta

Origin blog.csdn.net/qq_44918090/article/details/133349652
Recomendado
Clasificación