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接收的缓冲区,一个发送的缓冲区1
SSL_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();
else
do_ssl_connect();
}
else
LOG_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;
}
}
标签:muduo,函数,Helper,ssl,void,ret,SSL,程序设计
From: https://www.cnblogs.com/wuhaiqiong/p/18580662