c++ boost-asio

c++ - ¿Utilizando sockets SSL y sockets no SSL simultáneamente en Boost.Asio?



boost-asio (4)

Estoy en el proceso de convertir una biblioteca a Boost.Asio (que ha funcionado muy bien hasta ahora), pero me he topado con un obstáculo con respecto a una decisión de diseño.

Boost.Asio proporciona soporte para SSL, pero se debe usar un tipo boost::asio::ssl::stream<boost::asio::ip::tcp::socket> para el socket. Mi biblioteca tiene la opción de conectarse a servidores SSL o conectarse normalmente, por lo que he creado una clase con dos sockets como este:

class client : public boost::enable_shared_from_this<client> { public: client(boost::asio::io_service & io_service, boost::asio::ssl::context & context) : socket_(io_service), secureSocket_(io_service, context) {} private: boost::asio::ip::tcp::socket socket_; boost::asio::ssl::stream<boost::asio::ip::tcp::socket> secureSocket_; };

Y dentro hay un montón de manejadores que hacen referencia a socket_ . (Por ejemplo, tengo socket_.is_open() en varios lugares, lo que tendría que convertirse en secureSocket_.lowest_layer().is_open() para el otro socket).

¿Alguien puede sugerir la mejor manera de hacer esto? Preferiría no crear una clase separada solo para este propósito, porque eso significaría duplicar una gran cantidad de código.

Edición: Reformé mi pregunta original porque entendí mal el propósito de una función OpenSSL.


El problema, por supuesto, es que tcp :: socket y el "socket" ssl no comparten ningún ancestro común. Pero la mayoría de las funciones para usar el socket una vez que está abierto comparten la misma sintaxis. La solución más limpia es por lo tanto con plantillas.

template <typename SocketType> void doStuffWithOpenSocket(SocketType socket) { boost::asio::write(socket, ...); boost::asio::read(socket, ...); boost::asio::read_until(socket, ...); // etc... }

Esta función funcionará con tcp :: sockets normales y también sockets SSL seguros:

boost::asio::ip::tcp::socket socket_; // socket_ opened normally ... doStuffWithOpenSocket<boost::asio::ip::tcp::socket>(socket_); // works! boost::asio::ssl::stream<boost::asio::ip::tcp::socket> secureSocket_; // secureSocket_ opened normally (including handshake) ... doStuffWithOpenSocket(secureSocket_); // also works, with (different) implicit instantiation! // shutdown the ssl socket when done ...


Hay un par de maneras en que puedes hacer esto. En el pasado, he hecho algo como

if ( sslEnabled ) boost::asio::async_write( secureSocket_ ); } else { boost::asio::async_write( secureSocket_.lowest_layer() ); }

Lo que puede ensuciarse bastante rápido con muchas declaraciones if/else . También puede crear una clase abstracta (pseudo código - simplificado en exceso)

class Socket { public: virtual void connect( ... ); virtual void accept( ... ); virtual void async_write( ... ); virtual void async_read( ... ); private: boost::asio::ip::tcp::socket socket_; };

Luego cree un SecureSocket clase SecureSocket para operar en un secureSocket_ lugar de socket_ . No creo que esté duplicando una gran cantidad de código, y probablemente esté más limpio que if/else siempre que necesite async_read o async_write .


Se compilaría con algo como esto:

typedef boost::asio::buffered_stream<boost::asio::ip::tcp::socket> Socket_t;


Soy bastante tarde para responder esta pregunta, pero espero que esto ayude a otros. La respuesta de Sam contiene el germen de una idea, pero en mi opinión no deja de ser lo suficientemente lejos.

La idea surgió de la observación de que asio envuelve un socket SSL en una transmisión. Todo lo que hace esta solución es que envuelve el socket no SSL de manera similar.

El resultado deseado de tener una interfaz externa uniforme entre sockets SSL y no SSL se realiza con tres clases. Uno, la base, define efectivamente la interfaz:

class Socket { public: virtual boost::asio::ip::tcp::socket &getSocketForAsio() = 0; static Socket* create(boost::asio::io_service& iIoService, boost::asio::ssl::context *ipSslContext) { // Obviously this has to be in a separate source file since it makes reference to subclasses if (ipSslContext == nullptr) { return new NonSslSocket(iIoService); } return new SslSocket(iIoService, *ipSslContext); } size_t _read(void *ipData, size_t iLength) { return boost::asio::read(getSocketForAsio(), boost::asio::buffer(ipData, iLength)); } size_t _write(const void *ipData, size_t iLength) { return boost::asio::write(getSocketForAsio(), boost::asio::buffer(ipData, iLength)); } };

Dos subclases envuelven sockets SSL y no SSL.

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> SslSocket_t; class SslSocket: public Socket, private SslSocket_t { public: SslSocket(boost::asio::io_service& iIoService, boost::asio::ssl::context &iSslContext) : SslSocket_t(iIoService, iSslContext) { } private: boost::asio::ip::tcp::socket &getSocketForAsio() { return next_layer(); } };

y

class NonSslSocket: public Socket, private Socket_t { public: NonSslSocket(boost::asio::io_service& iIoService) : Socket_t(iIoService) { } private: boost::asio::ip::tcp::socket &getSocketForAsio() { return next_layer(); } };

Cada vez que llama a una función asio, use getSocketForAsio (), en lugar de pasar una referencia al objeto Socket. Por ejemplo:

boost::asio::async_read(pSocket->getSocketForAsio(), boost::asio::buffer(&buffer, sizeof(buffer)), boost::bind(&Connection::handleRead, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));

Observe que el zócalo se almacena como puntero. No puedo pensar de qué otra manera se puede ocultar el polimorfismo.

La penalización (que no me parece excelente) es el nivel adicional de indirección utilizado para obtener sockets que no son SSL.