一、🔍 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 里的最佳應用場景!