【发布时间】:2021-05-27 09:18:34
【问题描述】:
测试由服务器和客户端来回发送固定大小的消息,重复很长时间。它们都是单线程的,并且使用 Boost ASIO 库用 C++ 编写。通信是本地的,并且通过带有 TLS 的 TCP。我正在使用 bmon 监控环回接口,并使用 System Monitor 检查 CPU 使用情况。
当我使用 16384 的消息大小时,我看到 RX/TX 速度约为 200 MiB/s,CPU 使用率约为 60%。当我将消息大小更改为 16385 时,RX/TX 下降到大约 300 KiB/s,CPU 使用率下降到一个很小的百分比 (
我想知道吞吐量大幅下降的原因是什么?我怀疑它与 TLS 有关,因为使用纯 TCP 时没有下降。然而,2000:3 的下降似乎相当剧烈,尤其是考虑到该程序似乎也不受 CPU 限制。
我目前的猜测是 16384 是硬编码/配置的限制(可能是 TLS 最大记录大小?),超过这个数字需要额外的消息传递,但为什么吞吐量不会下降 ~2:1 而不是 2000:3 ?谁能帮忙解释一下?
这是完整的示例代码:
服务器.cpp
#include <functional>
#include <iostream>
#include <memory>
#include <system_error>
#include "asio.hpp"
#include "asio/ssl.hpp"
#define MESSAGE_SIZE 16385
class Server
{
public:
struct Certs
{
std::string certificate_chain_file;
std::string private_key_file;
std::string verify_file;
};
using Stream = asio::ssl::stream<asio::ip::tcp::socket>;
Server(Certs certs, unsigned short port)
: acceptor(io_context, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)),
ssl_context(asio::ssl::context::tlsv12),
rw_buf(new uint8_t[MESSAGE_SIZE])
{
ssl_context.set_options(
asio::ssl::context::default_workarounds |
asio::ssl::context::no_sslv2 |
asio::ssl::context::no_sslv3 |
asio::ssl::context::no_tlsv1 |
asio::ssl::context::no_tlsv1_1 |
asio::ssl::context::single_dh_use);
ssl_context.use_certificate_chain_file(certs.certificate_chain_file);
ssl_context.use_private_key_file(certs.private_key_file, asio::ssl::context::pem);
ssl_context.set_verify_mode(
asio::ssl::context::verify_peer |
asio::ssl::context::verify_fail_if_no_peer_cert);
ssl_context.load_verify_file(certs.verify_file);
acceptor.async_accept(std::bind(&Server::onAccept, this, std::placeholders::_1, std::placeholders::_2));
}
void run()
{
io_context.run();
}
void onAccept(const std::error_code& error, asio::ip::tcp::socket socket)
{
if (error)
{
std::cerr << "Accept error=" << error.message() << std::endl;
return;
}
stream.reset(new Stream(std::move(socket), ssl_context));
asyncHandshake();
}
void asyncHandshake()
{
stream->async_handshake(
asio::ssl::stream_base::server,
std::bind(&Server::onHandshake, this, std::placeholders::_1));
}
void onHandshake(const std::error_code& error)
{
if (error)
{
std::cerr << "Handshake error=" << error.message() << std::endl;
return;
}
asyncReadMessage();
}
void asyncReadMessage()
{
asio::async_read(
*stream,
asio::buffer(rw_buf.get(), MESSAGE_SIZE),
std::bind(&Server::onRead, this, std::placeholders::_1, std::placeholders::_2));
}
void asyncWriteMessage()
{
asio::async_write(
*stream,
asio::buffer(rw_buf.get(), MESSAGE_SIZE),
std::bind(&Server::onWrite, this, std::placeholders::_1, std::placeholders::_2));
}
void onRead(const std::error_code& error, size_t bytes_transferred)
{
if (error)
{
std::cerr << "Read error=" << error.message() << std::endl;
return;
}
asyncWriteMessage();
}
void onWrite(const std::error_code& error, size_t bytes_transferred)
{
if (error)
{
std::cerr << "Write error=" << error.message() << std::endl;
return;
}
asyncReadMessage();
}
protected:
asio::io_context io_context;
asio::ip::tcp::acceptor acceptor;
asio::ssl::context ssl_context;
std::unique_ptr<Stream> stream;
std::unique_ptr<uint8_t[]> rw_buf;
};
int main(int argc, char* argv[])
{
Server::Certs certs
{
.certificate_chain_file = "server.crt",
.private_key_file = "server.key",
.verify_file = "ca.crt"
};
unsigned short port = 9090;
Server server(certs, port);
server.run();
}
客户端.cpp
#include <functional>
#include <iostream>
#include <memory>
#include <system_error>
#include "asio.hpp"
#include "asio/ssl.hpp"
#define MESSAGE_SIZE 16385
#define MESSAGE_BURST_SIZE 50000
class Client
{
public:
struct Certs
{
std::string certificate_chain_file;
std::string private_key_file;
std::string verify_file;
};
using Stream = asio::ssl::stream<asio::ip::tcp::socket>;
Client(Certs certs)
: ssl_context(asio::ssl::context::tlsv12),
rw_buf(new uint8_t[MESSAGE_SIZE])
{
ssl_context.set_options(
asio::ssl::context::default_workarounds |
asio::ssl::context::no_sslv2 |
asio::ssl::context::no_sslv3 |
asio::ssl::context::no_tlsv1 |
asio::ssl::context::no_tlsv1_1 |
asio::ssl::context::single_dh_use);
ssl_context.use_certificate_chain_file(certs.certificate_chain_file);
ssl_context.use_private_key_file(certs.private_key_file, asio::ssl::context::pem);
ssl_context.set_verify_mode(
asio::ssl::context::verify_peer |
asio::ssl::context::verify_fail_if_no_peer_cert);
ssl_context.load_verify_file(certs.verify_file);
}
void run(std::string host, unsigned short port)
{
stream.reset(new Stream(std::move(asio::ip::tcp::socket(io_context)), ssl_context));
asio::ip::tcp::endpoint endpoint(asio::ip::address::from_string(host), port);
stream->lowest_layer().async_connect(endpoint, std::bind(&Client::onConnect, this, std::placeholders::_1));
io_context.run();
}
void onConnect(const std::error_code& error)
{
if (error)
{
std::cerr << "Connect error=" << error.message() << std::endl;
return;
}
asyncHandshake();
}
void asyncHandshake()
{
stream->async_handshake(
asio::ssl::stream_base::client,
std::bind(&Client::onHandshake, this, std::placeholders::_1));
}
void onHandshake(const std::error_code& error)
{
if (error)
{
std::cerr << "Handshake error=" << error.message() << std::endl;
return;
}
asyncWriteMessage();
}
void asyncReadMessage()
{
asio::async_read(
*stream,
asio::buffer(rw_buf.get(), MESSAGE_SIZE),
std::bind(&Client::onRead, this, std::placeholders::_1, std::placeholders::_2));
}
void asyncWriteMessage()
{
asio::async_write(
*stream,
asio::buffer(rw_buf.get(), MESSAGE_SIZE),
std::bind(&Client::onWrite, this, std::placeholders::_1, std::placeholders::_2));
}
void onRead(const std::error_code& error, size_t bytes_transferred)
{
if (error)
{
std::cerr << "Read error=" << error.message() << std::endl;
return;
}
if (++message_count >= MESSAGE_BURST_SIZE)
{
return;
}
asyncWriteMessage();
}
void onWrite(const std::error_code& error, size_t bytes_transferred)
{
if (error)
{
std::cerr << "Write error=" << error.message() << std::endl;
return;
}
asyncReadMessage();
}
protected:
asio::io_context io_context;
asio::ssl::context ssl_context;
std::unique_ptr<Stream> stream;
std::unique_ptr<uint8_t[]> rw_buf;
int message_count = 0;
};
int main(int argc, char* argv[])
{
Client::Certs certs
{
.certificate_chain_file = "client.crt",
.private_key_file = "client.key",
.verify_file = "ca.crt"
};
std::string host = "127.0.0.1";
unsigned short port = 9090;
Client client(certs);
client.run(host, port);
}
【问题讨论】:
-
"可能是 TLS 最大记录大小?"那是正确的。确切的值取决于密码套件,但它们都在 16K 附近。
标签: c++ sockets ssl boost-asio tls1.2