muduo实现ssl层的程序设计

news/2024/12/2 16:25:07/文章来源:https://www.cnblogs.com/wuhaiqiong/p/18580662

muduo的使用

muduo网络库内部分装了reactor和epoll以及socket,我们不需要知道其底层的内部封装;每次发生连接后都会调用连接onConnection的回调函数来处理连接。
每次当数据到达时都会调用onmessagecallback回调函数来执行数据的处理。

muduo增加ssl层

根据上一节,我们可以设计一个ssl_helper类,当发生连接时,就会调用ssl_helper的ssl握手的相关函数来执行ssl握手;
当数据到达时,我们就调用ssl_helper类的安全接收数据的函数即可;发送数据时也按照这样的步骤;客户端也是一样的。
处理接收数据时,首先会触发muduo的数据回调函数onmessage

ssl层处理连接的过程

  1. 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:如果是连接断开的,则需要将映射关系删除;
  2. SSL_Helper处理onSSLConnect的过程:
    1:需要判断连接是否已建立,如果不是则退出
    2:需要判断这个SSL_Helper的类型是什么,如果是server就执行do_ssl_accept()函数,否则执行do_ssl_connect()函数;
  3. 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层接收数据

  1. 触发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的区别

  1. BIO_new(BIO_s_mem())

    • 用途:创建一个新的 BIO 内存缓冲区。
    • 特点:这个 BIO 内存缓冲区可以存放数据,大小是动态变化的,可以根据需要增加或减少。
    • 使用场景:适用于临时数据存储、测试和调试等需要动态处理内存数据的情况。
  2. BIO_new_mem_buf

    • 用途:基于已存在的内存缓冲区创建一个 BIO 内存。
    • 特点:这个已存在的内存缓冲区作为 BIO 的数据源,因此 BIO 的大小是固定的,不会动态调整。
    • 使用场景:适用于读取或处理已经存在的内存数据,例如加载内存中的证书或密钥。

SSL_ReceiveData函数

SSL_ReceiveData函数内部是调用的绑定的可调用对象的函数,这个可调用对象的函数是通过set_receive_callback进行设置的,而这个可调用对象就是服务器或客户端
进行数据处理的具体的逻辑函数。例如服务端数据处理的函数

SSL握手注意事项

SSL握手是依赖于底层通信的实现的,我们需要实现SSL_processing_sendSSL_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;}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/845480.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

摄像机实时接入分析平台视频分析网关机动车结构化识别算法:“智眼识车”的技术革新

随着智能交通系统的发展,视频分析技术在机动车识别和监控中的应用越来越广泛。视频分析网关作为这一技术的核心组件,利用先进的算法对机动车进行结构化识别,以提升交通管理的效率和准确性。本文将探讨摄像机实时接入分析平台视频分析网关中机动车结构化识别算法的原理和应用…

HCIP-14 BGP基础

本文介绍了BGP基础知识,涵盖了:BGP产生的背景、AS的概念、BGP的特征等。 本文中我们详细地学习了BGP的对等体关系建立过程以及BGP状态机,学习时将对等体关系建立过程与状态机的转换相结合有助于理解记忆。不同于IGP路由协议,BGP不能自己发现、计算路由条目,其路由条目由IG…

【SpringCloud】LoadBalancer——服务调用与负载均衡

基本介绍 主要功能:LoadBlancer的主要作用就是提供客户端软件的负载均衡,然后由OpenFeign去调用具体的微服务。负载均衡的算法,分为轮询和随机。 使用 场景: 订单模块的负载均衡。通过消费者模块,访问订单支付模块(子模块8001/8002/8003) 例子前提:已经使用了注册服务中…

VMware Cloud Director 10.6 - 领先的云服务交付平台

VMware Cloud Director 10.6 - 领先的云服务交付平台VMware Cloud Director 10.6 - 领先的云服务交付平台 VMware Cloud Director | Leading Cloud Service Delivery Platform 请访问原文链接:https://sysin.org/blog/vmware-cloud-director-10/ 查看最新版。原创作品,转载请…

Winform窗体控件双向绑定数据模拟读写PLC数据

1.用Modbus工具模拟PLC2.创建一个实体类点击查看代码 internal class Data : INotifyPropertyChanged {ushort[] ushorts = new ushort[10];public ushort D0 { get => ushorts[0]; set { ushorts[0] = value; OnPropertyChanged(nameof(D0)); } }public ushort D1 { get =&…

解决:点击编辑时预警通知人(与科室下拉联动)会闪一下Id再显示汉字

表单:<el-form-item label="通知人所在科室:" prop="ksdm"><el-selectv-model="formData.ksdm"placeholder="请选择"style="width: 90%"clearablefilterable@change="handleKsChange"><el-option…

浏览器拨测:将网站护航的阵地再前推一米

建立对整个服务周期的可观测能力并对其中潜在的攻击做出识别,是保证内容合规和服务质量的重要的基础能力。近年来,针对网站的攻击形式愈发多样,手段也变得更加隐蔽,使用浏览器拨测来监控服务的整个生命周期有助于及时发现攻击,保护核心业务链路不受损。作者:泉思 “从你在…

Diffusion Model的数学原理

基本概念 Forward ProcessReverse Process

高效办公必备!四款好用的电脑桌面日程提醒软件推荐

很多打工人每天的日程安排非常多,很容易会忘记或遗漏。如果能给在电脑桌面上直接使用一款日程提醒软件,就可以准时提醒每项日程事项了,轻松提高办公效率! 下面介绍4款好用的电脑桌面日程体系软件! 一、Win系统自带日历 点击电脑桌面右下角的日期,可以在日历中的某个日期下…

2024年10款最受欢迎的项目管理工具,你用过几款?

项目管理工具在现代工作中扮演着至关重要的角色,2024 年又有哪些最受欢迎的工具呢?本文将为你详细介绍 10 款热门项目管理工具,快来看看你用过几款。 在当今快节奏的商业环境中,项目管理的效率直接影响着企业的发展和竞争力。随着科技的不断进步,各种项目管理工具层出不穷…

2099年:IDEA 2024.3 安装激活使用教程

IntelliJ IDEA简介 IntelliJ IDEA是一款非常强大的Java集成开发环境(IDE),由JetBrains公司开发。它提供了丰富的功能和工具,帮助开发者更高效地编写、调试和部署代码。 要求 在开始之前,请确保您的计算机满足以下系统要求:操作系统:Windows、macOS或Linux 处理器:至少1 GH…