C++ 代码实现局域网即时通信功能 (windows 系统 客户端)

news2024/9/22 11:30:17

本项目使用C++实现具备多个客户端和服务器端即时通信聊天功能软件

一:项目内容
使用C++实现一个具备多客户端和一个服务器端即时通信功能的聊天软件。
本项目的目的是
学习在windows平台下,进行C++网络开发的基本概念:TCP/IP socket通信,多线程编程,文件配置读写和通信协议制定等;
二:需求分析
这个聊天室主要有两个程序:
1.服务端:能够接受新的客户连接,并将每个客户端发来的信息,转发给对应的目标客户端。
2.客户端:能够连接服务器,并向服务器发送消息,同时可以接收服务器发来的消息。
属于C/S模型。
三:抽象与细化
服务端类需要支持:
1.支持多个客户端接入,实现聊天室基本功能。
2.启动服务,建立监听端口等待客户端连接。
3.使用epoll机制实现并发,增加效率。
4.客户端连接时,发送欢迎消息,并存储连接记录。
5.客户端发送消息时,根据消息类型,广播给所有用户(群聊)或者指定用户(私聊)。
6.客户端请求退出时,对相应连接信息进行清理。
客户端类需要支持:
1.连接服务器。
2.支持用户输入消息,发送给服务端。
3.接受并显示服务端发来的消息。
4.退出连接。
四:C/S模型
C/S模型图
五:涉及数据读写、转发等操作所以需要使用Windows 下IOCP模型:
 IOCP 全称I/O Completion Port,中文译为I/O完成端口。IOCP是一个异步I/O的Windows API,它可以高效地将I/O事件通知给应用程序,类似于Linux中的Epoll,详细信息请参考linux之epoll。
 I/O 完成端口可以充分利用 Windows 内核来进行 I/O 调度,相较于传统的Winsock模型,IOCP的优势主要体现在两方面:独特的异步I/O方式和优秀的线程调度机制。
  IOCP模型通信机制,主要过程为:1、socket关联iocp;2、在socket上投递I/O请求;3、事件完成返回完成通知封包;4、工作线程在iocp上处理事件。IOCP的这种工作模式:程序只需要把事件投递出去,事件交给操作系统完成后,工作线程在完成端口上轮询处理。该模式充分利用了异步模式高速率输入输出的优势,能够有效提高程序的工作效率。完成端口可以抽象为一个公共消息队列,当用户请求到达时,完成端口把这些请求加入其抽象出的公共消息队列。这一过程与多个工作线程轮询消息队列并从中取出消息加以处理是并发操作。这种方式很好地实现了异步通信和负载均衡,因为它使几个线程“公平地”处理多客户端的I/O,并且线程空闲时会被挂起,不会占用CPU周期。
    IOCP模型充分利用Windows系统内核,可以实现仅用少量的几个线程来处理和多个client之间的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能。

软件运行效果如下图:效果图
客户端详细代码如下:

/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    PublicDefine.h
 *@描述    公共数据结构定义
 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#pragma once
#include<stdio.h>
#include <iostream>
#include"winerror.h"
#define WIN32_LEAN_AND_MEAN
#include"Winsock2.h"



#define OutErr(a) std::cout << "error :" << (a) << std::endl \
      << "出错代码:"<< WSAGetLastError() << std::endl \
      << "出错文件:"<< __FILE__ << std::endl  \
      << "出错行数:"<< __LINE__ << std::endl \

#define OutMsg(a) std::cout << (a) << std::endl;



#define PORT            5050            // 监听端口
#define LOCAL_HOST      "127.0.0.1"     // 本地回路地址
#define DATA_BUFSIZE    8192

#define MAX_LISTEN_QUEUE    200

#define MAX_CONNECT      3000

#define MAX_DATA_LEN    2048        // 数据包长度

#define SEND_DATA_LEN    4096        // 发送数据包长度


/// 结构体定义
/*
 *@brief    用于IOCP的特定函数
 *@author   GhY
 *@date     2024/07/24
 */
typedef struct {
    OVERLAPPED Overlapped;
    WSABUF DataBuf;
    CHAR Buffer[DATA_BUFSIZE];
} PER_IO_OPERATION_DATA, *LPPER_IO_OPERATION_DATA;

/*
 *@brief    用于IOCP的特定结构
 *@author   GhY
 *@date     2024/07/24
 */
typedef struct _PER_HANDLE_DATA {
    SOCKET _socket;
    CHAR _ip[32];
    int _port;

    _PER_HANDLE_DATA()
    {
        _socket = NULL;
        memset(_ip, 0, 32);
        _port = -1;
    }
} PER_HANDLE_DATA, *LPPER_HANDLE_DATA;


#pragma pack(1)
/*
 *@brief    数据包头
 *@author   GhY
 *@date     2024/07/24
 */
typedef struct _DataHead {

    unsigned short  _type;      // 0=上传数据, 1=转发数据,2=请求数据
    unsigned int  _node;        // 客户端ID
    unsigned long   _time;

    _DataHead()
    {
        memset(this, 0, sizeof(_DataHead));
    }

} TcpHead, Udp_Head;

/*
 *@brief    数据包体
 *@author   GhY
 *@date     2024/07/24
 */
typedef struct _DataBody {
    char        _srcName[32];
    int         _length;
    char        _data[MAX_DATA_LEN];
    _DataBody()
    {
        memset(this, 0, sizeof(_DataBody));
    }

} TcpBody, UdpBody;

/*
 *@brief    发送数据
 *@author   GhY
 *@date     2024/07/24
 */
typedef struct _SendData {
    TcpHead _head;
    TcpBody _body;
} Tcp_SendData, Udp_SendData;

#pragma pack()


/*
 *@brief    socket连接管理
 *@author   GhY
 *@date     2024/07/24
 */
struct ClientManager {
    unsigned int _id;
    char _name[32];
    SOCKET _socket;
    char _addr[16];
    int _port;

    ClientManager()
    {
        memset(this, 0, sizeof(ClientManager));
    }

};

/*
 *@brief    通信消息
 *@author   GhY
 *@date     2024/07/24
 */
struct Message {
    unsigned int _sendId;
    char _send_name[32];
    unsigned int _receiverId;
    char _receiverName[32];
    char _data[MAX_DATA_LEN];

    Message()
    {
        memset(this, 0, sizeof(Message));
    }
};


/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    application.h
 *@描述    app基类
 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#ifndef __APPLICATION_H__
#define __APPLICATION_H__

class application
{
public:
    application();
    virtual ~application();

    /*
     *@brief Do some initialize before application lanuch
     */
    virtual bool initinstance();

    /*
    *@brief Run application
    */
    virtual int run();

    /*
    *@brief Exit application
    */
    virtual bool exitinstance();

};

#endif // !__APPLICATION_H__
#include "application.h"

application::application()
{
}

application::~application()
{
}

bool application::initinstance()
{
    return true;
}

int application::run()
{
    if (initinstance()) {
        run();
    }

    return exitinstance();
}

bool application::exitinstance()
{
    return true;
}
/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    client.h
 *@描述    客户端类声明
 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#ifndef  __CLIENT_H__
#define  __CLIENT_H__

#include<stdio.h>
#include<iostream>
#include "MySocket.h"
#include "MyReceive.h"
#include "application.h"
#include <string>
#include <vector>
#include <list>


class CCinData;


/*
 *@描述:    客户端类
 *@作者:    GhY
 *@日期:    2024/07/24
 *@历史:
 */
class CAppClient : public application, public sigslot::has_slots<>
{
public:
    CAppClient();

    ~CAppClient();

    /*
     *@brief    关联信号槽
     *@author   GhY
     *@date     2024/07/24
     */
    void InitSigslot();

    /*
     *@brief    初始化
     *@author   GhY
     *@date     2024/07/24
     */
    bool initinstance();

    /*
     *@brief    退出
     *@author   GhY
     *@date     2024/07/24
     */
    bool exitinstance();

    int run();
    /*
     *@desc       发送数据
     *@param:     sdata  待传输数据
     *@return     void
     *@author     GhY
     *@date       2024/07/24
     *@version    v1.0.0
     *@history:
     */
    void SendData(std::string* sdata);

public:
    MySocket* m_mysocket;

    std::list<std::string*> m_sendBufs;
    CCinData* m_sendData;
    bool m_exitFlag;   // 退出标志

protected:

private:
    MyReceive* m_myrev;
};

/*
 *@描述:    获取输入数据类
 *@作者:    GhY
 *@日期:    2024/07/24
 *@历史:
 */
class CCinData : public sigslot::has_slots<>
{
public:

    typedef sigslot::signal1<std::string* > SendDataEvent;

    SendDataEvent OnSendEvent;

public:

    CCinData(CAppClient* app);

    ~CCinData();

    void Run();

private:
    CAppClient* m_appClient;
    bool m_exitFlag;
};

#endif  //!__CLIENT_H__

/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    client.cpp
 *@描述    客户端类实现
 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#include "client.h"



DWORD WINAPI ClientCinProcess(LPVOID lpParam)
{
    CAppClient* appclient = (CAppClient*)lpParam;
    if (!appclient) {
        return 1;
    }
    if (appclient->m_sendData) {
        appclient->m_sendData->Run();
    }
    return 0;
}


DWORD WINAPI RunSendBufProcess(LPVOID lpParam)
{
    CAppClient* appclient = (CAppClient*)lpParam;
    if (!appclient) {
        return 1;
    }
    while (true) {
        if (appclient->m_exitFlag) {
            break;
        }
        if (appclient->m_sendBufs.empty()) {
            Sleep(300);
            continue;
        }

        if (appclient->m_sendBufs.size() > 0) {
            std::string* strBuf = appclient->m_sendBufs.front();
            appclient->m_sendBufs.pop_front();
            if (appclient->m_mysocket && !strBuf->empty()) {
                appclient->m_mysocket->SendData(*strBuf);
                delete strBuf;
            }
        }
    }
    return 0;
}


CAppClient::CAppClient()
{
    m_exitFlag = false;
    m_mysocket = new MySocket();
    std::string sIp = g_ConfigPtr.getConfigValueWithKey("net", "ip");
    std::string sPort = g_ConfigPtr.getConfigValueWithKey("net", "port");
    int iPort = sPort.empty() ? PORT : atoi(sPort.c_str());

    m_mysocket->InitData(sIp, iPort);
    m_mysocket->ClientConnect();

    m_myrev = new MyReceive(m_mysocket);

    m_sendBufs.clear();

    m_sendData = new CCinData(this);
    InitSigslot();
}

CAppClient::~CAppClient()
{
    this->disconnect_all();

    if (m_myrev) {
        delete m_myrev;
        m_myrev = nullptr;
    }
    if (m_mysocket) {
        m_mysocket->Close();
        delete m_mysocket;
        m_mysocket = nullptr;
    }

    if (m_sendData) {
        delete m_sendData;
        m_sendData = nullptr;
    }
}

bool CAppClient::initinstance()
{
    return true;
}

bool CAppClient::exitinstance()
{
    return true;
}

int CAppClient::run()
{
    std::cout << "使用说明:输入 quit 退出程序" << std::endl;

    std::string currentName = g_ConfigPtr.getConfigValueWithKey("base", "name");
    if (currentName.empty()) {
        char nameT[64] = { 0 };
        std::cout << "请输入名字:";
        std::cin >> nameT;
        g_ConfigPtr.SetConfigValue("base", "name", nameT);
    } else {
        std::cout << "当前用户名:" << currentName.c_str() << std::endl;
    }

    static int nCnt = 0;
    char sendBuf[2000] = { 0 };

    int recvdata = 0;

    HANDLE hProcessIO = CreateThread(NULL, 0, ClientCinProcess, this, 0, NULL);
    if (hProcessIO) {
        CloseHandle(hProcessIO);
    }

    HANDLE hProcessIO2 = CreateThread(NULL, 0, RunSendBufProcess, this, 0, NULL);
    if (hProcessIO2) {
        CloseHandle(hProcessIO2);
    }

    while (true) {
        if (m_exitFlag) {
            break;
        }
        recvdata = m_mysocket->ReceiveData();
        if (recvdata == 0) {
            Sleep(500);
        } else {
            recvdata = 0;
        }
    }
    return 0;
}

void CAppClient::SendData(std::string* sdata)
{
    std::string* tmpdata = sdata;
    if (tmpdata->empty()) {
        return;
    }
    if (tmpdata->compare("quit") == 0) {
        m_exitFlag = true;
        m_mysocket->Close();
        delete tmpdata;
        return;
    }

    m_sendBufs.push_back(tmpdata);
}

void CAppClient::InitSigslot()
{
    if (m_sendData) {
        m_sendData->OnSendEvent.connect(this, &CAppClient::SendData);
    }
}

CCinData::CCinData(CAppClient* app)
    : m_appClient(app)
    , m_exitFlag(false)
{}

CCinData::~CCinData()
{
    m_exitFlag = true;
}

void CCinData::Run()
{
    std::cout << "please cin message: " << std::endl;
    std::string* sendTest = new std::string("上线");
    OnSendEvent.emit(sendTest);

    while (true) {
        if (m_exitFlag) {
            break;
        }
        std::string* sendBuf = new std::string();
        //std::cin >> sendBuf;
        getline(std::cin, *sendBuf);
        if (!sendBuf->empty() && sendBuf->compare("quit") == 0) {
            m_exitFlag = true;
        }
        if (sendBuf->size() > 0) {
            OnSendEvent.emit(sendBuf);
        } else {
            Sleep(500);
        }
    }
}
/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    MyReceive.h
 *@描述    处理数据类声明
 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#ifndef  __MYRECEIVE_H__
#define __MYRECEIVE_H__
#include "MySocket.h"


/*
 *@描述:    接收数据处理类
 *@作者:    GhY
 *@日期:    2024/07/24
 *@历史:
 */
class MyReceive : public sigslot::has_slots<>
{
public:
    MyReceive(MySocket* s);
    ~MyReceive();

    /*
     *@brief    关联信号槽
     *@author   GhY
     *@date     2024/07/24
     */
    void InitSigslot();

    /*
     *@desc       接收数据
     *@param:     sdata  待接收数据
     *@return     void
     *@author     GhY
     *@date       2024/07/24
     *@version    v1.0.0
     *@history:
     */
    void ReceiveData(Tcp_SendData* sdata);

private:
    MySocket* m_mysocket;

};

#endif  //!__MYRECEIVE_H__

/****************************************************
 *
 *@Copyright (c) 2024, GhY, All rights reserved.
 *@文件    MyReceive.cpp
 *@描述    处理数据类实现
 *
 *@作者    GhY
 *@日期    2024年7月24日
 *@版本    v1.0.0
 *
 ****************************************************/
#include "MyReceive.h"

MyReceive::MyReceive(MySocket* s)
    : m_mysocket(s)
{
    InitSigslot();
}

MyReceive::~MyReceive()
{
    if (m_mysocket) {
        m_mysocket->disconnect_all();
    }
}

void MyReceive::InitSigslot()
{
    if (m_mysocket) {
        m_mysocket->OnSelectEvent.connect(this, &MyReceive::ReceiveData);
    }
}


void MyReceive::ReceiveData(Tcp_SendData* sdata)
{
    if (!sdata) {
        return;
    }
    do {
        if (sdata->_head._type == 2) {
            std::string tmp = sdata->_body._data;
            g_ConfigPtr.SetConfigValue("base", "id", tmp);
        } else {
            std::string tmpName = sdata->_body._srcName;
            std::string tmp = sdata->_body._data;
            std::cout << "send: " << tmpName.c_str() << " -- message:  " << tmp.c_str() << std::endl;
        }
    } while (0);

}

注意:

文章中依赖的文件(.h,.cpp)请参见本专栏其他文章。

源代码下载地址:源代码

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

西蒙学习法

西蒙学习法 一根筋&#xff0c;挖死坑&#xff1b;会思考&#xff0c;持续不断的思考&#xff1b;会问问题&#xff0c;有深度的问题&#xff1b;一直想一个问题的解决办法&#xff1b; 资料 《世界十大学习方法》之西蒙学习法

数据结构(5.3_3)——由遍历序列构造二叉树

若只给出一棵二叉树的前/中/后/层 序遍历序列中的一种&#xff0c;不能唯一确定一棵二叉树 构造二叉树 前序 中序遍历序列 例&#xff1a; 前序遍历序列&#xff1a;DAEFBCHGI 中序遍历序列&#xff1a;DAEFBCHGI 后序中序遍历序列 层序中序遍历 总结&#xff1a;

贪心算法(五) ----贪心+单调栈,poj-最佳加油方案

力扣316 ---去除重复字母 题目 给你一个字符串 s &#xff0c;请你去除字符串中重复的字母&#xff0c;使得每个字母只出现一次。需保证 返回结果的字典序最小&#xff08;要求不能打乱其他字符的相对位置&#xff09;。 示例 1&#xff1a; 输入&#xff1a;s "bcabc&q…

IDEA的pom.xml显示ignored 的解决办法

问题&#xff1a; idea中创建Maven module时&#xff0c;pom.xml出现ignored。 原因&#xff1a; 相同名称的module在之前被创建删除过&#xff0c;IDEA会误以为新的同名文件是之前删除掉的&#xff0c;将这个新的module的pom.xml文件忽略掉显示ignored. 解决&#xff1a; 在…

【Java有关链表OJ题】-- 单链表的逆置、获取链表的中间节点、获取倒数第k个节点、合并两个有序链表

1. 单链表的逆置 思路&#xff1a;通过头插节点来完成单链表的逆置&#xff0c;定义一个cur指向head的下一个节点&#xff0c;curNext记录cur的next节点&#xff0c; 当链表为空&#xff0c;即头节点head为空时&#xff0c;返回null。当链表只有一个head节点时&#xff0c;返…

【MySQL进阶之路 | 高级篇】数据并发问题与四种隔离级别

1. 事务隔离级别 MySQL是一个客户端/服务器架构的软件&#xff0c;对于同一个服务器来说&#xff0c;可以有若干个客户端与之连接&#xff0c;每个客户端与服务器连接之后&#xff0c;就可以称之为一个会话。每个客户端都可以在自己的会话中向服务器发出请求语句&#xff0c;一…

【电子通识】第一、二、三代半导体都是什么?

半导体指常温下导电性能介于导体与绝缘体之间的材料。半导体在集成电路、消费电子、通信系统、光伏发电、照明应用、大功率电源转换等领域应用。 如二极管就是采用半导体制作的器件。无论从科技或是经济发展的角度来看&#xff0c;半导体的重要性都是非常巨大的。 今日大部分的…

opencv入门(二)

文章目录 一、图像的基础操作1.1 图像ROI1.1.1 图像ROI理论介绍1.1.2 图像ROI的具体实现1.2 通道拆分与合并1.2.1 split():拆分通道1.2.2 merge():合并彩色分量图像1.3 图像的加法运算1.3.1 Numpy加法1.3.1 OpenCV加法1.4 图像融合1.4.1 图像加法1.4.2 图像融合1.4.3 注意点1…

sql server 连接报错error 40

做个简单的记录,造成40 的原因有很多,你的错误并不一定就是我遇到的这种情况. 错误描述: 首先我在使用ssms 工具连接的时候是可以正常连接的,也能对数据库进行操作. 在使用 ef core 连接 Sql Server 时报错: Microsoft.Data.SqlClient.SqlException (0x80131904): A network-r…

VIsual Studio:为同一解决方案下多个项目分别指定不同的编译器

一、引言 如上图&#xff0c;我有一个解决方案【EtchDevice】&#xff0c;他包含两个&#xff08;甚至更多个&#xff09;子项目&#xff0c;分别是【DeviceRT】和【DeviceWin】&#xff0c;见名知意&#xff0c;我需要一个项目编译运行在RTOS上&#xff0c;譬如一个名叫INTime…

Mailspring搭建安装教程:打造个性邮件体验

Mailspring搭建安装教程步骤&#xff01;如何选择电子邮件服务商&#xff1f; Mailspring作为一款功能强大、界面友好的邮件客户端&#xff0c;成为了许多用户的首选。AokSend将为大家提供详细的Mailspring搭建安装教程&#xff0c;帮助您打造个性化的邮件体验。 Mailspring搭…

智能APK动态防护系统:自动重命名与签名,实现安全分发

本智能APK动态防护系统通过集成先进的自动化处理技术&#xff0c;实现了对APK文件的深度定制化与安全性强化。系统核心功能包括自动反编译APK、随机生成包名与签名、代码混淆等&#xff0c;最终回编译生成独一无二的APK安装包。这一过程每5分钟&#xff08;时间间隔可自定义&am…

从java到JDBC学习笔记

java编写代码入门 1. 简介 JDK : Java SE Development Kit Java开发工具 2.第一段代码 main快捷键 msvm加回车 控制台输出 sout回车 删除一行 Ctrly 复制一行 Ctrld 空构造器 altinsert 撤销快捷键 CtrlZ 首先输入psvm创建主程序&#xff0c;我们的代码在这里边编写。…

java面向对象进阶进阶篇--《抽象类和抽象方法》

个人主页VON 所属专栏java从入门到起飞 目录 个人主页​编辑我的主页​编辑 一、简介 抽象方法&#xff1a; 抽象类&#xff1a; 概述&#xff1a; 二、抽象类 特点和用途 示例&#xff1a; Animal类 Dog类 Flog类 Sheep类 Text类 结果展示&#xff1a; 三、抽象方…

鸿蒙OpenHarmony Native API【raw_dir.h与raw_file.h】 头文件

raw_dir.h Overview Related Modules: [Rawfile] Description: 提供rawfile目录相关功能 功能包括遍历和关闭rawfile目录 Since: 8 Version: 1.0 Summary Typedefs Typedef NameDescription[RawDir]typedef struct [RawDir] 提供对rawfile目录的访问 Functions Fun…

Centos7下安装配置最新版本Jenkins

1、基础环境配置 1.1 服务器下载Jenkins安装包 下载地址&#xff1a;Download and deploy 下载命令&#xff1a;wget https://get.jenkins.io/war-stable/2.452.3/jenkins.war 1.2 服务器安装配置JDK Jenkins 是基于 Java 语言开发的&#xff0c;因此需要 Java 运行环境支…

【Mysql】Docker下Mysql8数据备份与恢复

[TOC] 【Mysql】Docker下Mysql8数据备份与恢复 1 创建Mysql容器 格式 docker run -d --name容器名称 -p 宿主端口号:3306 -e MYSQL_ROOT_PASSWORDmysql密码 -e MYSQL_PASSWORDmysql密码 -e TZAsia/Shanghai -v 宿主目录-数据:/var/lib/mysql -v 宿主目录-备份数据:/back…

在vue中优雅地异步引入(懒加载)腾讯地图API

背景 接到一个需求需要在网站首页显示使用腾讯地图展示公司所在地。一开始我直接全局引入了腾讯地图js&#xff0c;结果发现在用户打开登陆页面的时候首页比较缓慢&#xff0c;为了提高用户登陆的加载效率&#xff0c;需要优化为异步引入。 思路 根据官网的示例&#xff0c;…

2024世界技能大赛某省选拔赛“网络安全项目”B模块--操作系统取证解析

2024世界技能大赛某省选拔赛“网络安全项目”B模块--操作系统取证解析 任务一、操作系统取证解析:总结:任务一、操作系统取证解析: A 集团某电脑系统被恶意份子攻击并控制,怀疑其执行了破坏操作,窃取了集团内部的敏感信息,现请分析 A 集团提供的系统镜像和内存镜像,找到…

缓存穿透,缓存击穿,缓存雪崩

目录 介绍 缓存穿透 缓存击穿 缓存雪崩 原因 影响 解决方案 缓存穿透 防止缓存穿透->空值缓存案例 缓存击穿 使用互斥锁解决缓存击穿 介绍 缓存穿透 定义&#xff1a;缓存穿透是指用户查询数据&#xff0c;缓存和数据库中都不存在该数据&#xff08;一般是发起恶意…