Подтвердить что ты не робот

Использование сокетов SSL и не-SSL-сокетов одновременно в Boost.Asio?

Я собираюсь преобразовать библиотеку в Boost.Asio(который работал очень хорошо до сих пор), но я ударил что-то вроде камнем преткновения в отношении дизайнерского решения.

Boost.Asio обеспечивает поддержку SSL, но для сокета должен использоваться тип boost::asio::ssl::stream<boost::asio::ip::tcp::socket>. В моей библиотеке есть возможность подключиться к серверам SSL или нормально подключиться, поэтому я создал класс с двумя сокетами, например:

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_;
};

И внутри есть куча обработчиков, которые ссылаются на socket_. (Например, у меня есть socket_.is_open() в нескольких местах, что должно стать secureSocket_.lowest_layer().is_open() для другого сокета.)

Кто-нибудь может предложить лучший способ сделать это? Я бы предпочел не создавать отдельный класс именно для этой цели, потому что это означало бы дублирование большого количества кода.

Изменить: я перефразировал свой оригинальный вопрос, потому что неправильно понял назначение функции OpenSSL.

4b9b3361

Ответ 1

Есть несколько способов, которыми вы можете это сделать. Раньше я делал что-то вроде

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

Что может быть довольно грязным с большим количеством операторов if/else. Вы также можете создать абстрактный класс (псевдокод - упрощенное)

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

Затем создайте производный класс SecureSocket для работы с secureSocket_ вместо socket_. Я не думаю, что это было бы дублирование большого количества кода и, вероятно, более чистое, чем if/else, когда вам нужно async_read или async_write.

Ответ 2

Я довольно поздно отвечаю на этот вопрос, но надеюсь, что это поможет другим. Ответ Сэма содержит зародыш идеи, но, по моему мнению, не уходит достаточно далеко.

Идея возникла из того, что asio обертывает SSL-сокет в потоке. Все это решение заключается в том, что он похож на не-SSL-сокет.

Желаемый результат наличия единого внешнего интерфейса между SSL и не-SSL-сокетами выполняется с тремя классами. Одна, база, эффективно определяет интерфейс:

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));
    }
};

Два подкласса включают SSL и не 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();
    }
};

и

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();
    }
};

Каждый раз, когда вы вызываете функцию asio, используйте getSocketForAsio(), а не передавайте ссылку на объект Socket. Например:

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));

Обратите внимание, что Socket хранится как указатель. Я не могу думать, как иначе можно скрыть полиморфизм.

Штраф (что я не считаю большим) - это дополнительный уровень косвенности, используемый для получения сокетов без SSL.

Ответ 3

Конечно, проблема заключается в том, что tcp:: socket и ssl "socket" не имеют общего общего предка. Но большинство функций для использования сокета, когда он открывается, используют тот же синтаксис. Таким образом, самое чистое решение имеет шаблоны.

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

Эта функция будет работать с обычными tcp:: сокетами, а также защищать сокеты SSL:

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

Ответ 4

Он будет компилироваться с чем-то вроде этого:

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