User Tools

Site Tools


Asio SSL multithread server

main.cpp

main.cpp
/*
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 */
 
#include <iostream>
#include <string>
#include <asio.hpp>
 
#include "server.hpp"
 
int main(int argc, char* argv[]) {
    try {
        srv7::server server("0.0.0.0", "1026", 5);
        server.run();
    } catch (std::exception& e) {
        std::cerr << "exception: " << e.what() << "\n";
    }
    return 0;
}

server.hpp

server.hpp
/*
 *
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 *
 */
 
#ifndef SRV7_SERVER_HPP
#define SRV7_SERVER_HPP
 
#include <string>
#include <vector>
 
#include <asio.hpp>
#include <asio/ssl.hpp>
 
#include "connection.hpp"
 
namespace srv7 {
 
class server {
  private:
    std::size_t thread_pool_size;
    asio::io_context io_context;
    asio::signal_set signals;
    asio::ip::tcp::acceptor acceptor;
    asio::ssl::context ssl_context;
 
    std::shared_ptr<connection> connection;
 
    void accept();
    void stop();
 
  public:
    explicit server(
        const std::string& address,
        const std::string& port,
        std::size_t thread_pool_size
    );
 
    void run();
 
    server(const server&) = delete;
    server& operator=(const server&) = delete;
 
};
 
} // namespace srv7
 
#endif // SRV7_SERVER_HPP

server.cpp

server.cpp
/*
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 */
 
#include <iostream>
#include <vector>
#include <memory>
 
#include <asio.hpp>
#include <asio/ssl.hpp>
 
#include "server.hpp"
 
namespace srv7 {
 
server::server(const std::string& address, const std::string& port, std::size_t thread_pool_size)
    : thread_pool_size(thread_pool_size),
      signals(io_context),
      acceptor(io_context),
      ssl_context(asio::ssl::context::sslv23),
      connection()
    {
 
    signals.add(SIGINT);
    signals.add(SIGTERM);
 
    auto signal_handler = [this](std::error_code ec, int signo) {
        stop();
    };
 
    signals.async_wait(signal_handler);
 
    ssl_context.set_options(
        asio::ssl::context::default_workarounds
        | asio::ssl::context::no_sslv2
        | asio::ssl::context::single_dh_use
    );
    ssl_context.use_certificate_chain_file("server.pem");
    ssl_context.use_private_key_file("server.pem", asio::ssl::context::pem);
 
    asio::ip::tcp::resolver resolver(io_context);
    asio::ip::tcp::endpoint endpoint = *resolver.resolve(address, port).begin();
 
    acceptor.open(endpoint.protocol());
    acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true));
    acceptor.bind(endpoint);
    acceptor.listen(2048);
 
    this->accept();
}
 
void server::run() {
    std::vector<std::shared_ptr<asio::thread>> threads;
    for (std::size_t i = 0; i < thread_pool_size; ++i) {
 
        auto f = [this]{ io_context.run(); };
        auto t = new asio::thread(std::move(f));
 
        std::shared_ptr<asio::thread> thread(std::move(t));
        threads.push_back(thread);
    }
    for (std::size_t i = 0; i < threads.size(); ++i) {
        threads[i]->join();
    }
}
 
void server::accept() {
    connection.reset(new class connection(ssl_context, io_context));
    auto handler = [this](const asio::error_code& ec) {
        if (!ec) {
            connection->start();
        }
        this->accept();
    };
    acceptor.async_accept(connection->get_socket(), handler);
}
 
void server::stop() {
    io_context.stop();
}
 
} // namespace srv7

connection.hpp

connection.hpp
/*
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 */
 
 
#ifndef SRV7_CONNECTION_HPP
#define SRV7_CONNECTION_HPP
 
#include <memory>
#include <array>
 
#include <asio.hpp>
#include <asio/ssl.hpp>
 
namespace srv7 {
 
class connection : public std::enable_shared_from_this<connection> {
  private:
    asio::io_context::strand strand;
    asio::ip::tcp::socket socket;
    asio::ssl::context& ssl_context;
 
    asio::ssl::stream<asio::ip::tcp::socket> *ssl_socket;
 
    std::string response;
    std::string buffer;
 
    void handshake();
    void read();
    void write();
  public:
    explicit connection(
        asio::ssl::context& ssl_context,
        asio::io_context& io_context
    );
    connection(const connection&) = delete;
    connection& operator=(const connection&) = delete;
 
    asio::ip::tcp::socket& get_socket();
 
    void start();
    void stop();
    ~connection();
};
 
} // namespace srv7
 
#endif // SRV7_CONNECTION_HPP

connection.cpp

connection.cpp
/*
 * Copyright 2004-2019 Oleg Borodin  <borodin@unix7.org>
 */
 
#include <vector>
#include <sstream>
#include <filesystem>
#include <utility>
#include <iomanip>
#include <chrono>
 
#include <asio.hpp>
#include <asio/ssl.hpp>
 
#include "connection.hpp"
 
namespace srv7 {
 
connection::connection(asio::ssl::context& ssl_context, asio::io_context& io_context)
    : strand(io_context), socket(io_context), ssl_context(ssl_context) {}
 
asio::ip::tcp::socket& connection::get_socket() {
    return socket;
}
 
void connection::start() {
    ssl_socket = new asio::ssl::stream<asio::ip::tcp::socket>(
        std::move(socket),
        ssl_context
    );
    handshake();
}
 
void connection::handshake() {
        auto self(shared_from_this());
        ssl_socket->async_handshake(asio::ssl::stream_base::server,
        [this, self](const std::error_code& error) {
            if (!error) {
                this->read();
            } else {
                this->stop();
            }
        });
}
 
void connection::read() {
    auto self(shared_from_this());
    auto handler = [this, self](std::error_code ec, std::size_t size) {
        if (!ec) {
            this->write();
        } else if (ec != asio::error::operation_aborted) {
            this->stop();
        }
    };
    std::string delimeter = "\r\n\r\n";
    asio::async_read_until(
        *ssl_socket, asio::dynamic_buffer(buffer),
        delimeter,
        handler
    );
}
 
std::string timestamp() {
    std::stringstream ss;
    std::time_t now = std::time(0);
    ss << std::put_time(std::gmtime(&now), "%a, %d %b %Y %T %Z");
    return ss.str();
}
 
void connection::write() {
 
    std::stringstream content;
    for (int i = 0; i < 10000; i++) {
        content << "hello!";
    }
 
    std::stringstream header;
    header << "HTTP/1.1 200 OK\r\n";
    header << "Date: " << timestamp() << "\r\n";
    header << "Server: Srv7/0.1\r\n";
    header << "Content-Length: " << content.str().length() << "\r\n";
    header << "Content-Type: text/plain\r\n";
    header << "Connection: close\r\n";
    header << "\r\n";
 
    response += header.str();
    response += content.str();
 
    auto self(shared_from_this());
    auto handler = [this, self](std::error_code ec, std::size_t) {
        if (!ec) {
            this->stop();
        }
        if (ec != asio::error::operation_aborted) {
            this->stop();
        }
    };
    asio::async_write(
        *ssl_socket, asio::buffer(response),
        asio::bind_executor(strand, handler)
    );
}
 
void connection::stop() {
}
 
connection::~connection() {
    delete ssl_socket;
}
 
} // namespace srv7
# ab -c 200 -n100000 'https://localhost:1026/'
This is ApacheBench, Version 2.3 <$Revision: 1826891 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        Srv7/0.1
Server Hostname:        localhost
Server Port:            1026
SSL/TLS Protocol:       TLSv1.2,AES256-GCM-SHA384,2048,256
TLS Server Name:        localhost

Document Path:          /
Document Length:        60000 bytes

Concurrency Level:      200
Time taken for tests:   177.891 seconds
Complete requests:      100000
Failed requests:        0
Total transferred:      6014200000 bytes
HTML transferred:       6000000000 bytes
Requests per second:    562.14 [#/sec] (mean)
Time per request:       355.782 [ms] (mean)
Time per request:       1.779 [ms] (mean, across all concurrent requests)
Transfer rate:          33016.00 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        7  102  60.3     86     571
Processing:    31  253  74.7    269     613
Waiting:        2   34  24.3     26     230
Total:         94  356  77.2    358     705

Percentage of the requests served within a certain time (ms)
  50%    358
  66%    383
  75%    402
  80%    413
  90%    447
  95%    485
  98%    520
  99%    546
 100%    705 (longest request)
$ curl -vk 'https://localhost:1026/'
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 1026 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /usr/local/etc/ca-bundle.crt
  CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: C=US; CN=unix7.org
*  start date: Oct  6 07:28:03 2017 GMT
*  expire date: Oct  5 23:37:00 2019 GMT
*  issuer: C=ES; O=StartCom CA; OU=StartCom Certification Authority; CN=StartCom BR SSL ICA
*  SSL certificate verify result: self signed certificate in certificate chain (19), continuing anyway.
> GET / HTTP/1.1
> Host: localhost:1026
> User-Agent: curl/7.48.0
> Accept: */*
> 
< HTTP/1.1 200 OK
< Date: Sat, 27 Apr 2019 20:31:44 UTC
< Server: Srv3/0.1
< Content-Length: 60000
< Content-Type: text/plain
< Connection: close
< 
hello!hello! ....