2011-01-18 24 views
21

Estoy en el proceso de convertir una biblioteca a Boost.Asio (que ha funcionado muy bien hasta ahora), pero he tropezado con un obstáculo en lo que respecta a una decisión de diseño.¿Usa sockets SSL y sockets no SSL simultáneamente en Boost.Asio?

Boost.Asio proporciona compatibilidad con SSL, pero se debe utilizar 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 conectar con normalidad, por lo que he hecho una clase con dos tomas como esta:

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 los manipuladores que hacen referencia a socket_. (Por ejemplo, tengo socket_.is_open() en varios lugares, que necesitarían convertirse en secureSocket_.lowest_layer().is_open() para el otro socket).

¿Alguien puede sugerir la mejor manera de hacerlo? Prefiero no crear una clase separada solo para este propósito, porque eso significaría duplicar mucho código.

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

Respuesta

14

Hay varias formas de hacerlo. En el pasado, he hecho algo como

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

¿Qué puede causar problemas muy rápidamente con una gran cantidad de if/else declaraciones. También puede crear una clase abstracta (pseudo código - simplificado)

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

A continuación, cree una clase derivada SecureSocket para operar en un lugar de secureSocket_socket_. No creo que esté duplicando mucho código, y probablemente sea más limpio que if/else cuando necesite async_read o async_write.

+0

El primer enfoque es lo que terminé haciendo, pero me gusta la idea abstracta de clase. Lo echaré un vistazo, gracias. – DSB

17

Llego un poco tarde respondiendo esta pregunta, pero espero que esto ayude a los demás. La respuesta de Sam contiene el germen de una idea, pero no deja de ir lo suficientemente lejos en mi opinión.

La idea surgió de la observación de que asio ajusta un socket SSL en una transmisión. Todo lo que hace esta solución es que envuelve el socket que no es 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 se llama a una función asio utilizar 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 Socket se almacena como puntero. No puedo pensar cómo se puede ocultar el polimorfismo.

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

+0

parece que falta el 'typedef' para' Socket_t'. Además, ¿puede explicar por qué funciona esto, es decir, qué hace 'next_layer()' para cada tipo de socket? – yhager

+0

Gracias, Roman, por la corrección. – Nicole

+1

@yhager, la forma en que hice este trabajo es copiar la forma en que un socket SSL se deriva de un socket no SSL. Asio crea un socket SSL envolviéndolo en un contenedor que proporciona alguna funcionalidad adicional y que intercepta algunas actividades al agregarles un sabor SSL. El código que utiliza dicho socket SSL necesitará usar 'next_layer()' para obtener acceso al socket subyacente para algunas actividades comunes a los sockets SSL y no SSL. Toda mi sugerencia es envolver el socket de tal manera que se pueda tratar como si fuera un socket SSL, pero sin implementar ninguna actividad relacionada con SSL. – Nicole

1

Sería compilar con algo como esto:

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

3

El problema, por supuesto, es que TCP :: zócalo y la "toma" ssl no comparten ninguna el ancestro común. Pero la mayoría de las funciones para usar el socket una vez que están abiertas comparten la misma sintaxis exacta. 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 se activará con el trabajo normal de TCP :: enchufes y tomas también SSL seguras:

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 ...