一、🔍 SslConnection::SslConnection()
详解
这个构造函数的主要作用是:
- 创建 SSL 对象
- 创建 BIO(I/O 缓冲区)
- 初始化 SSL 服务器模式
- 绑定回调函数(
onRead()
处理接收数据)
📌 1. 初始化 SSL 相关对象
SslConnection::SslConnection(const TcpConnectionPtr& conn, SslContext* ctx)
: ssl_(nullptr) // SSL对象(加密/解密)
, ctx_(ctx) // SSL 上下文
, conn_(conn) // Muduo 连接对象(底层网络传输)
, state_(SSLState::HANDSHAKE) // 初始状态:TLS 握手
, readBio_(nullptr) // 读 BIO(接收加密数据)
, writeBio_(nullptr) // 写 BIO(存储加密数据)
, messageCallback_(nullptr) // 业务逻辑的回调(不一定用得上)
💡 这里主要是初始化了一些成员变量,确保构造对象时数据是干净的。
📌 2. 创建 SSL 上下文
ssl_ = SSL_new(ctx_->getNativeHandle());
if (!ssl_) {
LOG_ERROR << "Failed to create SSL object: " << ERR_error_string(ERR_get_error(), nullptr);
return;
}
📍 作用
- 通过
SSL_new(ctx_->getNativeHandle())
创建 SSL 对象,用于加解密。 - 如果失败,打印日志并返回。
🔹 ctx_->getNativeHandle()
是什么?
- 这里的
ctx_
是SslContext*
,表示 SSL 的全局上下文。 ctx_->getNativeHandle()
返回的是 OpenSSL 的SSL_CTX*
,它管理 证书、加密方式、TLS 版本等。SSL_new()
基于SSL_CTX*
创建一个 新的 SSL 会话对象,用于该 TCP 连接的数据加解密。
📌 3. 创建 BIO(I/O 缓冲区)
readBio_ = BIO_new(BIO_s_mem());
writeBio_ = BIO_new(BIO_s_mem());
if (!readBio_ || !writeBio_) {
LOG_ERROR << "Failed to create BIO objects";
SSL_free(ssl_);
ssl_ = nullptr;
return;
}
📍 作用
readBio_ = BIO_new(BIO_s_mem())
:创建 读 BIO,用于存放接收的加密数据(网络数据)。writeBio_ = BIO_new(BIO_s_mem())
:创建 写 BIO,用于存放加密后的待发送数据。- 这里使用
BIO_s_mem()
,表示创建的是 内存 BIO(不会自动关联 socket,手动读写)。 - 如果创建 BIO 失败,则释放 SSL 资源,防止内存泄露。
📌 4. 绑定 SSL 和 BIO
SSL_set_bio(ssl_, readBio_, writeBio_);
📍 作用
- 让 OpenSSL 通过 BIO 读写数据,而不是直接操作 socket。
readBio_
存放接收到的加密数据(模拟 OpenSSL 的SSL_read()
)。writeBio_
存放加密后的待发送数据(模拟SSL_write()
)。SSL_set_bio()
绑定 BIO 后,SSL_read()
会自动从readBio_
读取数据,SSL_write()
会把加密数据存入writeBio_
。
💡 这样就实现了 OpenSSL 和 Muduo 的解耦:
- Muduo 只管传输数据(读写
readBio_
和writeBio_
)。 - SSL 只管加密/解密(不直接接触网络)。
📌 5. 设置服务器模式
SSL_set_accept_state(ssl_); // 设置为服务器模式
📍 作用
- 服务器模式:在 TLS 握手时,服务器等待客户端发起
Client Hello
。 - 服务器通过
SSL_accept()
进行 TLS 握手,成功后才能进行加密通信。
📌 6. 设置 SSL 选项
SSL_set_mode(ssl_, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE);
📍 作用
🔹 SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
- 允许
SSL_write()
使用动态内存,不要求数据缓冲区保持固定位置(适用于异步网络)。
🔹 SSL_MODE_ENABLE_PARTIAL_WRITE
- 允许
SSL_write()
部分写入,在数据较长时,不会阻塞,而是先写入部分数据,后续继续发送。
📌 7. 绑定数据接收回调
conn_->setMessageCallback(
std::bind(&SslConnection::onRead, this, std::placeholders::_1,
std::placeholders::_2, std::placeholders::_3));
📍 作用
conn_
是 Muduo 的 TCP 连接,它接收加密数据后,会调用onRead()
处理。onRead()
主要作用:- 先把 加密数据 写入
readBio_
。 - 调用
SSL_read()
解密数据。 - 解密后的数据交给业务逻辑处理。
- 先把 加密数据 写入
🎯 8. 总结
🚀 整个 SslConnection
的构造逻辑可以总结如下:
- 创建 SSL 对象(用于加密/解密)。
- 创建 BIO(
readBio_
存接收数据,writeBio_
存待发送数据)。 - 绑定 BIO(让
SSL_read()
/SSL_write()
读写 BIO)。 - 设置 SSL 服务器模式(
SSL_set_accept_state()
)。 - 设置 SSL 选项(允许
SSL_write()
使用动态内存、支持部分写入)。 - 绑定
onRead()
回调,处理 Muduo 网络层的数据。
💡 9. 你的代码和 Muduo/SSL 之间的关系
- Muduo 负责底层的
TCP
连接和数据收发。 - SSL 负责 TLS/SSL 加解密,但不直接读写 socket,而是通过
BIO
缓冲区进行数据交互。 - Muduo 只管传输,SSL 只管加解密,BIO 作为中间桥梁。
🚀 这套架构解耦了网络层和加密层,这样可以:
- 更灵活:BIO 让 SSL 适配不同的 I/O(可以换成文件、内存等)。
- 更高效:Muduo 依旧是 非阻塞 IO,BIO 负责数据缓存,不阻塞线程。
- 更安全:SSL 加密的数据 Muduo 无法直接解读,避免安全漏洞。
🛠 10. 你可以改进的地方
✅ 异常处理更全面
- 目前如果
SSL_new()
失败,只是打印日志并return
,可以抛出异常或者返回错误码,更容易排查问题。
✅ 考虑 BIO_set_mem_eof_return()
BIO_s_mem()
可能会在BIO_read()
读取完数据后返回 0,导致SSL_read()
误以为连接断开。可以用:
这样BIO_set_mem_eof_return(readBio_, -1); BIO_set_mem_eof_return(writeBio_, -1);
SSL_read()
还能继续读取新数据。
这部分代码设计得 很合理,核心逻辑非常清晰,已经符合 高性能、可扩展、解耦的要求!🚀
二、为什么使用BIO_s_mem()
OpenSSL 里 BIO(Basic I/O) 有多种类型,而 BIO_s_mem()
只是其中之一。
你可以理解 BIO 就是 OpenSSL 里数据的输入/输出缓冲区,不同类型的 BIO 代表不同的 I/O 方式。
📌 1. OpenSSL 里的 BIO 类型
OpenSSL 提供了 三种主要 BIO,分别是:
BIO 类型 | 描述 | 适用于 |
---|---|---|
BIO_s_socket() | 直接操作 socket 读写 | 阻塞式网络编程 |
BIO_s_mem() | 纯内存缓冲区,需要手动传输数据 | 非阻塞网络、框架 |
BIO_s_ssl() | 直接包裹 SSL* ,自动处理 | 内部封装更简单 |
🚀 你这里用了 BIO_s_mem()
,它的特点是:
✅ 不会自动关联 socket(灵活,可适配任何网络框架)
✅ 手动读写数据(Muduo 负责收发,BIO 只是缓存)
✅ 适用于事件驱动框架(如 Muduo)
📌 2. BIO_s_mem()
为什么适合 Muduo?
Muduo 是 非阻塞 + 事件驱动 的框架,而 OpenSSL 默认的 SSL_read()
/ SSL_write()
设计成了 阻塞式,所以直接用 OpenSSL 的 BIO_s_socket()
会导致阻塞整个事件循环,影响服务器性能。
💡 解决方案:使用
BIO_s_mem()
让 Muduo 负责数据收发,OpenSSL 只管加解密。
🌟 BIO_s_mem()
允许这样做:
- Muduo 读取 TCP 数据,然后 写入
readBio_
:BIO_write(readBio_, encryptedData, len);
- OpenSSL 从
readBio_
里读取数据并解密:SSL_read(ssl_, decryptedBuffer, len);
- OpenSSL 处理完数据,调用
SSL_write()
生成加密数据,存入writeBio_
:SSL_write(ssl_, plaintextData, len);
- Muduo 负责从
writeBio_
读取加密数据并发送给客户端:BIO_read(writeBio_, encryptedBuffer, len); sendToClient(encryptedBuffer, len);
✅ 这样,Muduo 仍然是非阻塞的,BIO 只是一个中间缓冲区,不会影响 Muduo 的事件循环。
📌 3. 其他 BIO 方式为什么不合适?
🚫 BIO_s_socket()
直接绑定 socket
如果你用了 BIO_s_socket()
,那么:
SSL_read()
/SSL_write()
会直接阻塞,导致 Muduo 线程被卡住。- 影响服务器性能,不能高并发处理多个连接。
🚫 BIO_s_ssl()
让 OpenSSL 直接处理 I/O
- 这个 BIO 适用于简单场景,适合自己管理 socket 的程序,比如同步服务器。
- 但你用的是 Muduo 事件驱动,要自己控制数据流动,所以不适合。
📌 4. 总结
你这里使用 BIO_s_mem()
的原因:
- 适配 Muduo 非阻塞模型,不会让
SSL_read()
阻塞线程。 - 灵活控制数据流动,BIO 只是缓冲区,Muduo 仍然管理 TCP 读写。
- 高性能,避免 OpenSSL 直接操作 socket 导致的效率问题。
🚀 这就是 BIO_s_mem()
在 Muduo 里的最佳应用场景!