muduo的使用
muduo网络库内部分装了reactor和epoll以及socket,我们不需要知道其底层的内部封装;每次发生连接后都会调用连接onConnection的回调函数来处理连接。
每次当数据到达时都会调用onmessagecallback回调函数来执行数据的处理。
muduo增加ssl层
根据上一节,我们可以设计一个ssl_helper类,当发生连接时,就会调用ssl_helper的ssl握手的相关函数来执行ssl握手;
当数据到达时,我们就调用ssl_helper类的安全接收数据的函数即可;发送数据时也按照这样的步骤;客户端也是一样的。
处理接收数据时,首先会触发muduo的数据回调函数onmessage
ssl层处理连接的过程
- muduo的tcpserver中的连接回调函数绑定了sslserver的函数onServerConnection回调函数,当TCP连接完成时或连接中断时,就会执行这个回调函数,这个回调函数的主要步骤是:
1:设置连接非延迟;
2:创建SSL_Helper对象,并设置角色为server;
3:需要将TcpConnectionPtr和SSL_Helper对象放入map中,这么做的目的是为了根据连接的指针,找到对应的ssl,因为数据发送到服务器时会触发onMessage回调函数,而这个回调函
数的第一个参数就是TcpConnectionPtr,因此我们需要根据这个指针,传递给对应的SSL_Helper对象进行处理
4:设置SSL层解密完数据后,处理数据的函数,即绑定处理数据的回调函数;
5:调用SSL_Helper类的onSSLConnect函数来处理TCP连接后的SSL握手过程;
6:如果是连接断开的,则需要将映射关系删除; - SSL_Helper处理onSSLConnect的过程:
1:需要判断连接是否已建立,如果不是则退出
2:需要判断这个SSL_Helper的类型是什么,如果是server就执行do_ssl_accept()函数,否则执行do_ssl_connect()函数; - do_ssl_accept()的执行过程:
1:初始化openssl及ssl(调用四个函数,网上可以搜索);
2:创建ssl上下文,并设置服务器不验证对端的公钥证书;
3:创建ssl指针对象;
4:服务端需要设置SSL证书;
5:创建BIO的接收缓冲区,并和ssl对象进行绑定,这么做的目的是为了后续当数据到来时ssl,知道从哪个bio缓冲区去取数据或写数据。(2-5都写在一个函数中)
6:调用SSL_set_accept_state(m_Ssl)表示已经准备好进行ssl握手了,SSL_set_accept_state(m_Ssl)是一个宏;
7:调用SSLProcessingAccept()来进行处理,本质是ssl_read,当ssl_read发现握手还没完成时,就会自动触发握手;
SSL层接收数据
- 触发muduo网络库的数据接收回调函数onMessage,这个函数内部会调用ssl层的onSSLMessage函数,并将数据传递给ssl层;
onSSLMessage函数:
1:调用SSLProcessingRecv函数来执行,这个函数内部主要是讲数据写入到bio内存缓冲区中,然后ssl_read就会从这个bio中解密并读取数据;
2:调用SSLReceiveData()函数就来处理数据,这个函数内部回调用绑定的服务器的特定的数据处理的回调函数,即1.4绑定的函数;
SSL发送数据
SSL发送数据首先调用SSLSendData函数,这个函数内部使用ssl_write将数据写入到BIO内存中,然后再调用SSLProcessingSend()函数;
SSLProcessingSend()函数,内部主要是用于从bio中读取数据到指定的缓冲区中,然后再调用muduo的TcpConnectionPtr的send方法发送数据出去;
BIO的创建方式
BIO_new(BIO_s_mem()) 和BIO_new_mem_buf的区别
-
BIO_new(BIO_s_mem())
:- 用途:创建一个新的 BIO 内存缓冲区。
- 特点:这个 BIO 内存缓冲区可以存放数据,大小是动态变化的,可以根据需要增加或减少。
- 使用场景:适用于临时数据存储、测试和调试等需要动态处理内存数据的情况。
-
BIO_new_mem_buf
:- 用途:基于已存在的内存缓冲区创建一个 BIO 内存。
- 特点:这个已存在的内存缓冲区作为 BIO 的数据源,因此 BIO 的大小是固定的,不会动态调整。
- 使用场景:适用于读取或处理已经存在的内存数据,例如加载内存中的证书或密钥。
SSL_ReceiveData函数
SSL_ReceiveData
函数内部是调用的绑定的可调用对象的函数,这个可调用对象的函数是通过set_receive_callback进行设置的,而这个可调用对象就是服务器或客户端
进行数据处理的具体的逻辑函数。例如服务端数据处理的函数
SSL握手注意事项
SSL握手是依赖于底层通信的实现的,我们需要实现SSL_processing_send
和SSL_processing_Recv
函数来处理基于muduo网络库的ssl层的数据通信服务,
即当SSL层的握手数据到达时,也是会触发Muduo网络库的onMessage
的回调函数的,然后我们就需要通过这个函数来调用基于ssl层的数据接收和发送函数。
SSL_processing_send
函数,本质就是SSL_write将数据加密后填入到bio缓冲区中,然后我们需要从这个缓冲区中读取数据,并发送出去;
SSL_processing_Recv
函数,本质是将加密的数据写入到bio缓冲区中,然后就会使用ssl_read解密数据并填写到解密数据的缓冲区中,然后再通过SSL_ReceiveData()
函数调用绑定的数据处理函数;而如果握手还未完成,则ssl_read会自动进行握手;
SSL层SSL_Helper实现
SSL_Helper.h
/********************************************************************************* @file : SSL_Helper.h* @author : sally* @brief : None* @attention : None* @date : 24-12-1*******************************************************************************/#ifndef SSL_HELPER_H
#define SSL_HELPER_H
#include <muduo/net/TcpConnection.h>
#include <muduo/net/TcpServer.h>
#include <muduo/base/Timestamp.h>
#include <muduo/base/Logging.h>
#include <openssl/ssl.h>
#include <iostream>//muduo产生每个连接时,就会调用连接的回调函数,我们需要设置连接的回调函数
extern const char * ca_cert_key_pem;
extern const char * server_cert_key_pem;enum SSL_OPERATION_TYPE
{RECV = 0,SEND = 1
};enum SSL_TYPE
{SSL_SERVER = 0,SSL_CLIENT = 1
};//这个类封装了SSL握手过程,以及这个层需要封装接收数据后的实际处理的回调函数,但是这个回调函数是通过设置的方式绑定其它的类的函数的。
class SSL_Helper
{
public:SSL_Helper(const muduo::net::TcpConnectionPtr &conn);~SSL_Helper();//写一个连接建立后muduo网络库会直接调用ssl层的连接函数void onSSLConnection(const muduo::net::TcpConnectionPtr &conn);void setSSLType(SSL_TYPE type){m_sslType = type;}void onSSLMessage(const muduo::net::TcpConnectionPtr &conn,muduo::net::Buffer* buff,muduo::Timestamp time);void set_connected_callback(std::function<void()> &&func){m_SSL_connected_callback_ = func;}void set_receive_callback(std::function<int(SSL_Helper*,unsigned char *,size_t)> &&func){m_SSL_receive_callback_ = func;}void set_close_callback(std::function<void()> &&func){m_SSL_closed_callback_ = func;}private:void init_ssl();//创建服务端的ssl上下文函数void createServerSSLContext();//创建客户端的ssl上下文函数void createClientSSLContext();//还需要设置证书void setSSLCertificate();void close_session();int do_ssl_accept();int do_ssl_connect();//这个函数就是处理ssl握手的void SSL_processing_accept(); //这里是服务器进行ssl握手的函数void SSL_processing_connect(); //这是客户端的void SSL_processing_send(); //这是处理数据发送的void SSL_processing_Recv(const char * RecvBuffer,size_t BytesSizeRecieved); //这是处理数据接收的void ssl_connected() const;void SSL_ReceiveData();bool IsSSLError(int ssl_error);private:const muduo::net::TcpConnectionPtr m_connection_; //这里表示一个已经连接的tcp//发送和接收数据的缓冲区BIO *m_bio[2]; //这里表示的是一个0接收的缓冲区,一个发送的缓冲区1SSL_CTX *m_ssl_ctx_;SSL * m_ssl_;bool m_handshaked;//存放数据的缓冲区std::vector<unsigned char> m_EncryptSendData; //这是存放将要发送的数据std::vector<unsigned char> m_decryptRecvData; //这是存放接收后解密后的数据int m_SendSize;SSL_TYPE m_sslType;unsigned long long m_BytesSizeRecieved;unsigned long long m_TotalRecived;unsigned long long m_CurrRecived;//ssl连接建立完成后的回调函数std::function<void()> m_SSL_connected_callback_;//处理数据的回调函数,这个函数通常绑定的是server类的处理函数std::function<int(SSL_Helper*,unsigned char *,size_t)> m_SSL_receive_callback_;std::function<void()> m_SSL_closed_callback_;
};#endif //SSL_HELPER_H
SSL_Helper.cpp
/********************************************************************************* @file : SSL_Helper.cpp* @author : sally* @brief : None* @attention : None* @date : 24-12-1*******************************************************************************/#include "SSL_Helper.h"SSL_Helper::SSL_Helper(const muduo::net::TcpConnectionPtr& conn): m_connection_(conn),m_CurrRecived(0),m_BytesSizeRecieved(0),m_TotalRecived(0),m_handshaked(false)
{m_EncryptSendData.resize(1024 * 10);m_decryptRecvData.resize(1024 * 10);init_ssl();
}SSL_Helper::~SSL_Helper()
{
}void SSL_Helper::onSSLConnection(const muduo::net::TcpConnectionPtr& conn)
{if (conn->connected()){if (m_sslType == SSL_SERVER)do_ssl_accept();elsedo_ssl_connect();}elseLOG_WARN << "connect close";
}void SSL_Helper::onSSLMessage(const muduo::net::TcpConnectionPtr& conn, muduo::net::Buffer* buff, muduo::Timestamp time)
{std::cout << "receive data,size: " << buff->readableBytes() << std::endl;auto datalen = buff->readableBytes();m_BytesSizeRecieved += datalen;SSL_processing_Recv(buff->peek(),datalen); //处理数据buff->retrieveAll(); //处理完毕后重置缓冲区
}/*** 这个函数的作用就是初始化openssl的*/
void SSL_Helper::init_ssl()
{SSL_load_error_strings();SSL_library_init();OpenSSL_add_all_algorithms();
}/*** 这个函数的主要作用就是初始化ssl的上下文么ssl句柄的*/
void SSL_Helper::createServerSSLContext()
{m_ssl_ctx_ = SSL_CTX_new(SSLv23_method());//服务端设置不检查客户端的证书SSL_CTX_set_verify(m_ssl_ctx_,SSL_VERIFY_NONE, nullptr);//将ssl和证书关联起来setSSLCertificate();//创建ssl句柄m_ssl_ = SSL_new(m_ssl_ctx_);//创建bio内存m_bio[RECV] = BIO_new(BIO_s_mem());m_bio[SEND] = BIO_new(BIO_s_mem());SSL_set_bio(m_ssl_, m_bio[RECV], m_bio[SEND]); //这是将ssl句柄和bio绑定在一起
}void SSL_Helper::createClientSSLContext()
{m_ssl_ctx_ = SSL_CTX_new(SSLv23_method());//客户端需要设置验证服务端的公钥证书// SSL_CTX_set_verify(m_ssl_ctx_,SSL_VERIFY_PEER,nullptr);//客户端没有证书就不需要将证书和ssl上下文绑定起来//初始化ssl句柄m_ssl_ = SSL_new(m_ssl_ctx_);m_bio[RECV] = BIO_new(BIO_s_mem());m_bio[SEND] = BIO_new(BIO_s_mem());SSL_set_bio(m_ssl_, m_bio[RECV], m_bio[SEND]);
}void SSL_Helper::setSSLCertificate()
{int length = strlen(server_cert_key_pem);BIO* bio_cert = BIO_new_mem_buf((void*)server_cert_key_pem, length);X509* cert = PEM_read_bio_X509(bio_cert, nullptr, nullptr, nullptr);//获取私钥,从server_cert_key_pem中EVP_PKEY* prikey = PEM_read_bio_PrivateKey(bio_cert, nullptr, nullptr, nullptr);int ret = SSL_CTX_use_certificate(m_ssl_ctx_, cert);if (ret != 1)close_session();ret = SSL_CTX_use_PrivateKey(m_ssl_ctx_, prikey);if (ret != 1)close_session();X509_free(cert);EVP_PKEY_free(prikey);BIO_free(bio_cert);
}void SSL_Helper::close_session()
{m_connection_->forceClose();printf("close_session()\n");
}int SSL_Helper::do_ssl_accept()
{//调用函数创建ssl上下文createServerSSLContext();SSL_set_accept_state(m_ssl_);//处理握手的函数SSL_processing_accept();return 1;
}int SSL_Helper::do_ssl_connect()
{createClientSSLContext();SSL_set_connect_state(m_ssl_);SSL_processing_connect();return 1;
}void SSL_Helper::SSL_processing_accept()
{int ret;int ssl_error;int dwBytesSizeRecieved = 0;do{ret = SSL_read(m_ssl_, m_decryptRecvData.data(), m_decryptRecvData.size());ssl_error = SSL_get_error(m_ssl_, ret);if (IsSSLError(ssl_error))close_session();if (ret > 0)dwBytesSizeRecieved += ret;}while (ret > 0);//判断ssl握手是否已经完成,如果完成了则是调用的结束数据的函数if (SSL_is_init_finished(m_ssl_)){m_handshaked = true;SSL_ReceiveData();}//处理完毕就需要调用发送的函数SSL_processing_send();
}void SSL_Helper::SSL_processing_connect()
{int ret,ssl_error;int bytesSizeRecived = 0;do{ret = SSL_read(m_ssl_,m_decryptRecvData.data(),m_decryptRecvData.size());ssl_error = SSL_get_error(m_ssl_,ret);if(IsSSLError(ssl_error))close_session();if(ret >0)bytesSizeRecived += ret;}while (ret >0);if (SSL_is_init_finished(m_ssl_)){m_handshaked = true;SSL_ReceiveData();//receive data from ssl sockets}SSL_processing_send();
}/*** 这个函数主要就是用来将数据发送出去的,在调用这个函数之前,SSL_Write会将数据写入到BIO[SEND]中,然后我们需要将* 数据从Bio中读取到加密数据的缓冲区中*/
void SSL_Helper::SSL_processing_send()
{int ret;int ssl_error;while (BIO_pending(m_bio[SEND])){ret = BIO_read(m_bio[SEND], m_EncryptSendData.data(), m_EncryptSendData.size());if (ret > 0)m_connection_->send(m_EncryptSendData.data(), ret);else{ssl_error = SSL_get_error(m_ssl_, ret);if (IsSSLError(ssl_error))close_session();}}
}void SSL_Helper::SSL_processing_Recv(const char* RecvBuffer, size_t BytesSizeRecieved)
{int ret;int ssl_error;if (m_BytesSizeRecieved > 0){//将数据写入到bio缓冲区中,以便使用ssl_read函数从bio缓冲区中读取并解密ret = BIO_write(m_bio[RECV], RecvBuffer, BytesSizeRecieved);if (ret > 0){int intRet = ret;if (intRet > m_BytesSizeRecieved)close_session();m_BytesSizeRecieved -= intRet;}else{ssl_error = SSL_get_error(m_ssl_, ret);if (IsSSLError(ssl_error))close_session();}}do{assert(m_decryptRecvData.size() - m_CurrRecived >0);ret = SSL_read(m_ssl_, m_decryptRecvData.data() + m_CurrRecived, m_decryptRecvData.size() - m_CurrRecived);if (ret > 0){m_CurrRecived += ret;m_TotalRecived += ret;if (m_handshaked){SSL_ReceiveData();}}else{ssl_error = SSL_get_error(m_ssl_, ret);if (IsSSLError(ssl_error))close_session();}}while (ret > 0);if (!m_handshaked){if (SSL_is_init_finished(m_ssl_)){m_handshaked = true;ssl_connected(); //这是连接完成后的回调函数}}SSL_processing_send();
}void SSL_Helper::ssl_connected() const
{if(m_SSL_connected_callback_)m_SSL_connected_callback_();
}/*** 这里面封装的调用绑定的数据处理的逻辑函数*/
void SSL_Helper::SSL_ReceiveData()
{if(m_SSL_receive_callback_)m_SSL_receive_callback_(this,m_decryptRecvData.data(),m_decryptRecvData.size());//处理完成后,清空数据缓冲区m_decryptRecvData.clear();
}bool SSL_Helper::IsSSLError(int ssl_error)
{switch (ssl_error){case SSL_ERROR_NONE:case SSL_ERROR_WANT_READ:case SSL_ERROR_WANT_WRITE:case SSL_ERROR_WANT_CONNECT:case SSL_ERROR_WANT_ACCEPT:return false;default: return true;}
}