Asio SSL multithread server
main.cpp
- main.cpp
/*
* Author, Copyright: Oleg Borodin <onborodin@gmail.com>
*/
#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
/*
*
* Author, Copyright: Oleg Borodin <onborodin@gmail.com>
*
*/
#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
/*
* Author, Copyright: Oleg Borodin <onborodin@gmail.com>
*/
#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
/*
* Author, Copyright: Oleg Borodin <onborodin@gmail.com>
*/
#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
/*
* Author, Copyright: Oleg Borodin <onborodin@gmail.com>
*/
#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! ....