高性能的 C++ Web 开发框架 CPPCMS + WebSocket 模拟实现聊天与文件传输案例。

news2024/11/15 20:31:27

1. 项目结构

在这里插入图片描述

2. config.json

{
    "service": {
        "api": "http",
        "port": 8080,
        "ip": "0.0.0.0"
    },
    "http": {
        "script": "",
        "static": "/static"
    }
}

3. CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(c_web)

set(CMAKE_CXX_STANDARD 17)

# 指定源文件
set(SOURCE_FILES src/main.cpp src/blog.cpp)

# 手动设置 CppCMS 和 Booster 的头文件路径
include_directories(/usr/local/include)

# 手动设置库文件路径
link_directories(/usr/local/lib)

# 添加可执行文件
add_executable(c_web ${SOURCE_FILES})

find_package(Boost REQUIRED COMPONENTS system thread)
include_directories(${Boost_INCLUDE_DIRS})

# 添加 nlohmann_json 的头文件路径
include_directories(/usr/include/nlohmann)


# 链接 CppCMS 和 Booster 库
target_link_libraries(c_web cppcms booster ${Boost_LIBRARIES})

4. main.cpp

#include <cppcms/service.h>  // 引入CppCMS服务的头文件
#include <cppcms/applications_pool.h>  // 应用池管理类
#include <cppcms/http_response.h>  // 处理HTTP响应的类
#include <cppcms/url_dispatcher.h>  // URL调度器,用于将请求映射到处理程序
#include <boost/beast/core.hpp>  // Beast库的核心部分,用于处理输入输出操作
#include <boost/beast/websocket.hpp>  // Beast库的WebSocket部分,用于WebSocket处理
#include <boost/asio/ip/tcp.hpp>  // ASIO库的TCP/IP协议部分
#include <boost/asio/io_context.hpp>  // ASIO库的IO上下文,用于管理异步操作
#include <boost/asio/strand.hpp>  // ASIO库的strand,用于确保回调顺序
#include <thread>  // C++标准库的线程支持
#include <mutex>  // C++标准库的互斥锁,用于线程安全操作
#include <set>  // C++标准库的集合容器
#include <vector>  // C++标准库的向量容器
#include <iostream>  // 标准输入输出流
#include <fstream>  // 文件流,用于文件读写操作
#include <map>  // 映射容器,用于键值对存储
#include "blog.h"  // 自定义的博客应用程序头文件
#include <nlohmann/json.hpp>  // 引入nlohmann JSON库,用于处理JSON数据

namespace beast = boost::beast;  // 简化命名空间
namespace websocket = beast::websocket;  // 简化命名空间
namespace net = boost::asio;  // 简化命名空间
using tcp = net::ip::tcp;  // 使用TCP协议
using json = nlohmann::json;  // 使用nlohmann JSON库进行JSON处理

// 用于存储当前所有连接的用户及其对应的WebSocket连接
std::map<std::string, std::shared_ptr<websocket::stream<tcp::socket>>> user_connections;
// 用于确保线程安全地访问user_connections的互斥锁
std::mutex connections_mutex;

// 处理每个WebSocket会话的函数
void do_session(std::shared_ptr<websocket::stream<tcp::socket>> ws) {
    std::string current_user;  // 存储当前用户的用户名

    try {
        ws->accept();  // 接受WebSocket连接

        while(1) {  // 无限循环处理接收的消息
            beast::flat_buffer buffer;  // 创建一个缓冲区来存储接收到的数据
            ws->read(buffer);  // 从WebSocket连接中读取数据
            std::string message = beast::buffers_to_string(buffer.data());  // 将缓冲区中的数据转换为字符串

            auto data = json::parse(message);  // 解析JSON格式的数据

            // 处理用户初始化(登录)请求
            if (data["type"] == "init") {
                current_user = data["username"];  // 获取并保存当前用户的用户名
                {
                    // 使用互斥锁确保线程安全地更新user_connections
                    std::lock_guard<std::mutex> lock(connections_mutex);
                    user_connections[current_user] = ws;  // 将用户与其WebSocket连接关联
                }

                // 构建一个包含所有已连接用户列表的JSON对象
                json user_list = { {"type", "user_list"}, {"users", json::array()} };
                for (const auto& pair : user_connections) {
                    user_list["users"].push_back(pair.first);  // 将每个用户的用户名添加到列表中
                }

                // 将用户列表发送给所有已连接的用户
                for (const auto& pair : user_connections) {
                    pair.second->text(true);  // 设置为文本消息
                    pair.second->write(net::buffer(user_list.dump()));  // 发送用户列表
                }
            }
            // 处理普通消息传递请求
            else if (data["type"] == "message") {
                std::string target = data["target"];  // 获取消息的目标用户
                // 如果目标用户在线,则将消息转发给该用户
                if (user_connections.find(target) != user_connections.end()) {
                    auto target_ws = user_connections[target];
                    target_ws->text(true);  // 设置为文本消息
                    target_ws->write(net::buffer(message));  // 将消息发送给目标用户
                }
            }
            // 处理文件元数据及文件传输请求
            else if (data["type"] == "metadata") {
                std::string target = data["target"];  // 获取文件传输的目标用户
                // 如果目标用户在线,则发送文件元数据,并准备接收文件内容
                if (user_connections.find(target) != user_connections.end()) {
                    auto target_ws = user_connections[target];
                    target_ws->text(true);  // 发送文件元数据
                    target_ws->write(net::buffer(message));
                    beast::flat_buffer file_buffer;  // 准备接收文件内容的缓冲区
                    ws->read(file_buffer);  // 从发送者处读取文件内容
                    target_ws->binary(true);  // 设置为二进制消息
                    target_ws->write(file_buffer.data());  // 将文件内容转发给目标用户
                }
            }
        }
    } catch (std::exception const& e) {  // 捕获并处理异常
        std::cerr << "WebSocket Error: " << e.what() << std::endl;  // 打印错误信息
    }

    // 清理连接,确保在连接断开时从用户列表中移除用户
    std::lock_guard<std::mutex> lock(connections_mutex);
    if (!current_user.empty()) {
        user_connections.erase(current_user);  // 从连接映射中移除当前用户
    }
}

// 程序入口
int main(int argc, char* argv[]) {
    try {
        cppcms::service app(argc, argv);  // 创建CppCMS服务对象

        // 启动WebSocket服务器线程
        std::thread websocket_thread([]() {
            net::io_context ioc{1};  // 创建IO上下文对象
            tcp::acceptor acceptor{ioc, tcp::endpoint(tcp::v4(), 8081)};  // 创建TCP接受器,监听8081端口
            for (;;) {  // 无限循环,处理每个新的连接
                auto socket = std::make_shared<websocket::stream<tcp::socket>>(ioc);
                acceptor.accept(socket->next_layer());  // 接受新的TCP连接,并将其提升为WebSocket连接
                std::thread(&do_session, socket).detach();  // 为每个连接启动一个新的会话线程
            }
        });

        // 启动CppCMS服务
        app.applications_pool().mount(cppcms::applications_factory<blog>());  // 将博客应用挂载到服务池中
        app.run();  // 运行服务

        websocket_thread.join();  // 等待WebSocket线程结束
    } catch (std::exception const &e) {  // 捕获并处理异常
        std::cerr << "Error: " << e.what() << std::endl;  // 打印错误信息
        return EXIT_FAILURE;  // 以失败状态退出程序
    }
}

说明:WebSocket服务端口:8081,CppCMS服务端口:8080

5. blog.h

#ifndef BLOG_H
#define BLOG_H
#include <cppcms/application.h>
#include <cppcms/http_response.h>
#include <cppcms/url_dispatcher.h>
#include <cppcms/url_mapper.h>
#include <fstream>
class blog : public cppcms::application {
public:
    blog(cppcms::service &srv);
    void index();
private:
    void serve_html(const std::string &path);
	
};
#endif

6. blog.cpp

#include "blog.h"

blog::blog(cppcms::service &srv) : cppcms::application(srv) {
    dispatcher().map("GET", "/", &blog::index, this);
}

void blog::index() {
    serve_html("./views/index.html");
}

void blog::serve_html(const std::string &path) {
    std::ifstream file(path);
    if (file.is_open()) {
        std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
        response().out() << content;
    } else {
        response().status(404);
        response().out() << "Page not found";
    }
}

7. index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Transfer and Chat</title>
    <!-- 引入 Vue.js 和 Element-UI (包含图标支持) -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/element-ui@2.15.9/lib/theme-chalk/index.css">
    <script src="https://cdn.jsdelivr.net/npm/element-ui@2.15.9/lib/index.js"></script>
</head>
<body>
    <div id="app">
        <el-container>
            <el-header>
                <h2>实时聊天与文件传输</h2>
            </el-header>
            <el-main>
                <el-row :gutter="20">
                    <el-col :span="6">
                        <el-select v-model="selectedUser" placeholder="选择一个用户" style="width: 100%;">
                            <el-option
                                v-for="user in users"
                                :key="user"
                                :label="user"
                                :value="user">
                            </el-option>
                        </el-select>
                    </el-col>
                </el-row>
                <el-row>
                    <el-col :span="24">
                        <div id="chatbox" class="chatbox">
                            <el-card v-for="(msg, index) in messages" :key="index" class="box-card">
                                <p><strong>{{ msg.sender }}:</strong>
                                    <!-- 图片预览 -->
                                    <template v-if="msg.isImage">
                                        {{ msg.content }} <br>
                                        <img :src="msg.url" alt="Image Preview" style="max-width: 100px; max-height: 100px;">
                                    </template>
                                    <!-- 文件下载链接 -->
                                    <template v-else>
                                        {{ msg.content }} 
                                        <a :href="msg.url" :download="msg.filename" style="color: blue; text-decoration: underline;">
                                            {{ msg.filename }}
                                        </a>
                                    </template>
                                </p>
                                <p style="font-size: 0.85em; color: #888;">{{ formatDate(msg.timestamp) }}</p>
                            </el-card>
                        </div>
                    </el-col>
                </el-row>
                <el-row>
                    <el-col :span="18">
                        <el-input placeholder="输入消息..." v-model="newMessage" @keyup.enter.native="sendMessage"></el-input>
                    </el-col>
                    <el-col :span="6">
                        <el-button type="primary" icon="el-icon-send" @click="sendMessage">发送</el-button>
                    </el-col>
                </el-row>
                <el-row style="margin-top: 20px;">
                    <el-col :span="18">
                        <el-upload
                            class="upload-demo"
                            drag
                            action=""
                            :auto-upload="false"
                            :on-change="handleFileChange"
                            :file-list="fileList">
                            <i class="el-icon-upload"></i>
                            <div class="el-upload__text">拖拽文件到此或点击上传</div>
                        </el-upload>
                    </el-col>
                    <el-col :span="6">
                        <el-button type="success" icon="el-icon-upload2" @click="sendFile">发送文件</el-button>
                    </el-col>
                </el-row>
            </el-main>
        </el-container>
    </div>

    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    ws: null,
                    users: [],
                    selectedUser: null,
                    currentUser: null,
                    newMessage: '',
                    messages: [],
                    fileList: [],
                    pendingFileMessages: [] // 用于存储尚未处理的文件元数据
                };
            },
            mounted() {
                this.currentUser = prompt("请输入您的用户名:");
                this.ws = new WebSocket("ws://192.168.186.77:8081");

                this.ws.onopen = () => {
                    const initMessage = {
                        type: "init",
                        username: this.currentUser
                    };
                    this.ws.send(JSON.stringify(initMessage));
                };

                this.ws.onmessage = (event) => {
                    try {
                        const data = JSON.parse(event.data);

                        if (data.type === 'user_list') {
                            this.users = data.users.filter(user => user !== this.currentUser);
                        } else if (data.type === 'message' || data.type === 'metadata') {
                            data.timestamp = new Date();

                            if (data.type === 'metadata') {
                                this.pendingFileMessages.push(data); // 存储文件元数据
                            } else {
                                this.messages.push(data);
                            }
                        }
                    } catch (error) {
                        // 如果解析失败,可能是Blob数据
                        if (event.data instanceof Blob) {
                            this.handleFileData(event.data);
                        } else {
                            console.error("消息解析失败:", error);
                        }
                    }
                };
            },
            methods: {
                sendMessage() {
                    if (this.newMessage.trim() && this.selectedUser) {
                        const message = {
                            type: "message",
                            sender: this.currentUser,
                            target: this.selectedUser,
                            content: this.newMessage,
                            timestamp: new Date() // 增加时间戳
                        };
                        this.ws.send(JSON.stringify(message));
                        this.messages.push(message);
                        this.newMessage = '';
                    }
                },
                handleFileChange(file, fileList) {
                    this.fileList = fileList;
                },
                sendFile() {
                    if (this.fileList.length && this.selectedUser) {
                        const file = this.fileList[0].raw;
                        const reader = new FileReader();
                        reader.onload = (event) => {
                            const metadata = {
                                type: "metadata",
                                sender: this.currentUser,
                                target: this.selectedUser,
                                filename: file.name,
                                filesize: file.size,
                                timestamp: new Date().toISOString() // 增加时间戳
                            };
                            this.ws.send(JSON.stringify(metadata));

                            // 立即显示文件信息
                            const message = {
                                sender: this.currentUser,
                                content: ``,
                                isImage: this.isImageFile(file.name),
                                url: this.isImageFile(file.name) ? URL.createObjectURL(file) : '',
                                filename: file.name, // 保存文件名
                                timestamp: new Date() // 增加时间戳
                            };
                            this.messages.push(message);

                            this.ws.send(event.target.result);
                        };
                        reader.readAsArrayBuffer(file);
                        this.fileList = [];
                    }
                },
                handleFileData(blobData) {
                    // 处理文件数据时,检查是否有待处理的文件元数据
                    if (this.pendingFileMessages.length > 0) {
                        const metadata = this.pendingFileMessages.shift(); // 取出第一个元数据
                        const url = URL.createObjectURL(blobData);

                        const lastMessage = {
                            sender: metadata.sender,
                            content: ``,
                            isImage: this.isImageFile(metadata.filename),
                            url: url,
                            filename: metadata.filename, // 保存文件名
                            timestamp: metadata.timestamp
                        };

                        // 更新消息中的图片URL或生成下载链接
                        if (lastMessage.isImage) {
                            lastMessage.content = ``;
                        } else {
                            lastMessage.content = ``;
                        }

                        this.messages.push(lastMessage); // 将消息加入消息列表
                    } else {
                        console.error("未找到合适的文件元数据来处理文件数据。");
                    }
                },
                isImageFile(filename) {
                    return /\.(jpeg|jpg|gif|png|svg)$/i.test(filename);
                },
                formatDate(date) {
                    const d = new Date(date);
                    const year = d.getFullYear();
                    const month = ('0' + (d.getMonth() + 1)).slice(-2);
                    const day = ('0' + d.getDate()).slice(-2);
                    const hours = ('0' + d.getHours()).slice(-2);
                    const minutes = ('0' + d.getMinutes()).slice(-2);
                    const seconds = ('0' + d.getSeconds()).slice(-2);
                    return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
                }
            }
        });
    </script>

    <style>
        .chatbox {
            height: 300px;
            overflow-y: scroll;
            border: 1px solid #ddd;
            padding: 10px;
            margin-bottom: 20px;
        }
        .el-upload__input {
            display: none !important;
        }
    </style>
</body>
</html>

8. 测试验证

8.1 启动项目

cmake ./
make
./c_web -c ./config.json

在这里插入图片描述

说明:如果你想结束,请查询8081的PID,进行端口查杀kill -9 PID。

8.2 测试准备

访问:http://192.168.186.77:8080/

注意:IPv4需要切换为你本机IP。

在这里插入图片描述

说明:模拟用户数量:2,本案例是:admin和guest。

在这里插入图片描述

说明:聊天之前,先选择用户。

8.3 实时聊天

在这里插入图片描述

说明:左边是guest用户,右边是admin用户。

8.4 文件传输

在这里插入图片描述

说明:guest发送了一张图片给admin用户。

在这里插入图片描述

说明:admin发送了一个docx文件给guest。

在这里插入图片描述

说明:用户点击链接可以进行下载,下载不安全的原因是因为这是一个http而不是https。控制台描述:192.168.186.77/:1 The file at ‘blob:http://192.168.186.77:8080/997dc48a-83c2-4992-8ad3-960e7a51e74f’ was loaded over an insecure connection. This file should be served over HTTPS.

9. 总结

​ 只是实现了简单的实时聊天和文件传输,并没有严格的会话窗口区分,比如说如果A发送消息给B,C又发送给B,那么B窗口会同时显示A和C的消息,基于Booster库的Websocket实现简单案例。

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

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

相关文章

html+css+js网页设计 电商模版4个页面

htmlcssjs网页设计 电商模版4个页面 带js 网页作品代码简单&#xff0c;可使用任意HTML编辑软件&#xff08;如&#xff1a;Dreamweaver、HBuilder、Vscode 、Sublime 、Webstorm、Text 、Notepad 等任意html编辑软件进行运行及修改编辑等操作&#xff09;。 获取源码 1&…

我把AI洗脑了!看了潘展乐、全红婵的采访,我才知道:远离人情世故,就是远离内耗——早读(逆天打工人爬取热门微信文章解读)

想无 引言Python 代码第一篇 洞见看了潘展乐、全红婵的采访&#xff0c;我才知道&#xff1a;远离人情世故&#xff0c;就是远离内耗第二篇 讲个鬼故事结尾 引言 昨天晚上把干货赶出来了 也就是昆虫小达人 大家可以看看 大概率能够让你心理上克服昆虫恐惧 今天没什么重点得事…

一、软件工程概述

软件工程概述 1. 软件的概念和特点2. 软件危机的产生3. 软件工程的概念和发展过程4. 软件工程知识体系与职业道德 1. 软件的概念和特点 软件定义 软件程序数据文档。 软件生存周期 问题定义&#xff1a;要解决的问题是什么&#xff1f;可行性分析&#xff1a;对于上阶段所确定…

智慧农业新篇章:实时监测与精细化管理

智慧农业&#xff0c;作为现代农业技术革命的先锋&#xff0c;正引领着一场深刻的产业变革。它通过融合物联网、大数据、人工智能等前沿科技&#xff0c;实现了对农业生产环境的实时监测与精细化管理&#xff0c;开启了农业发展的新篇章。这一转型不仅提升了农产品的质量与产量…

云仓酒庄湖南团队启航新征程:筑基新质生产力,深耕啤酒屋市场

原标题&#xff1a;云仓酒庄湖南团队启航新征程&#xff1a;筑基新质生产力&#xff0c;深耕啤酒屋市场 在当前经济转型升级的关键期&#xff0c;新质生产力的崛起正成为推动行业发展的重要引擎。云仓酒庄湖南团队积极响应市场变革&#xff0c;率先启动基础培训项目&#xff0…

基于paddlehub 未戴口罩检测算法

一、简介 以前大夏天戴着口罩别人觉得你不正常&#xff0c;现在上街不戴口罩你不正常。 本文要讲的未戴口罩算法是基于paddlehub提供的模型&#xff0c;paddlehub是百度飞浆(PaddlePaddle)下的深度学习模型开发工具。 PaddleHub旨在为开发者提供丰富的、高质量的、直接可用的…

beautifulsoup的简单使用

文章目录 beautifulsoup一. beautifulsoup的简单使用1、安装2、如何使用3、对象的种类 二、beautifulsoup的遍历文档树2.1 子节点.contents 和 .children descendants2.2 节点内容.string.text 2.3 多个内容.strings**.stripped_strings** 2.4 父节点.parent.parents 三、beaut…

AD如何在封装制作时添加禁止铺铜区域?

在PCB封装库中&#xff0c;选择“Top Layer”层执行菜单命令“放置→多边形铺铜挖空”&#xff0c; 然后画好所需要的挖空的区域即可&#xff0c;如果是设计完PCB之后才来进行铺铜挖空的&#xff0c;可以在添加完铺铜挖空之后选中器件右击点选“Update PCB With All”&#x…

PyFluent入门之旅(10)Fluent Python Console

之前的文章中都在介绍如何在Fluent外部环境使用PyFluent&#xff0c;那么是否有可能在Fluent内部使用PyFluent呢&#xff1f; 自Ansys 2023 R1开始&#xff0c;Ansys Fluent的内置控制台支持Python命令&#xff0c;这使在Fluent内部控制台使用PyFluent成为了可能。 准备工作 …

【SpringBoot】9 定时任务(Quartz)

介绍 实现方式 java定时任务调度的实现方式&#xff1a;Timer&#xff0c;ScheduledExecutor&#xff0c;Spring Scheduler&#xff0c;JCron Tab&#xff0c;Quartz 等。 Quartz Quartz是一个由Java开发的开源项目&#xff0c;它可以与J2EE、J2SE应用程序相结合也可以单独…

直播预约|8月14日,无人系统开发阶段故障注入与安全测试详解

电机失效故障硬件在环仿真 01 培训背景 卓翼飞思实验室暑期公益培训(第六期)将于8月14日&#xff0c;19:00开启&#xff01;通过【飞思实验室】视频号线上直播&#xff0c;由中南大学计算机学院特聘副教授&#xff0c;RflySim平台总研发负责人戴训华副教授主讲。 第六期培训将…

Navicat Premium15 下载与安装(免费版)以及链接SqlServer数据库

转自:https://blog.csdn.net/m0_75188141/article/details/139842565

Hi910X 系列恒压恒流 BUCK 控制器

1. 产品介绍 Hi910X 是一系列外围电路简洁的宽输入电压降压 BUCK 恒压恒流驱动器&#xff0c;适用于 8-150V 输入电压范围的 DCDC 降压应用。Hi9100、Hi9101、Hi9102、Hi9103智芯半导体降压恒压系列 Hi910X 采用我司专利算法&#xff0c;实现高精度的降压恒压恒流。支持输出…

CV每日论文--2024.7.24

1 、AutoAD-Zero: A Training-Free Framework for Zero-Shot Audio Description 中文标题&#xff1a;T2V-CompBench&#xff1a;组合文本到视频生成的综合基准AutoAD-Zero&#xff1a;零样本音频描述的免训练框架 简介&#xff1a;我们的目标是以无需训练的方式为电影和电视剧…

入门岛第2关Python基础知识

任务一 :Python实现wordcount 实现一个wordcount函数&#xff0c;统计英文字符串中每个单词出现的次数。返回一个字典&#xff0c;key为单词&#xff0c;value为对应单词出现的次数。 任务二:debug流程 安装好Python插件在 远程服务器中 在程序中打断点 debug 点击VSCode侧…

haproxy是什么?以及haproxy基础实验

目录 一、什么是负载均衡&#xff1f; 二、为什么要用haproxy&#xff1f; 三、haproxy的基本部署实验&#xff1a; 3.1 基本配置实验 环境准备&#xff1a; 详细步骤&#xff1a; 3.2 haproxy-多进程与多线程实验&#xff1a; 多进程&#xff1a; 多线程&#xff1a;…

【云服务器】 阿里云服务器免费试用3个月 不用学生认证

审核大大&#xff0c;这个真的不是广告呀...也是我琢磨了一下发现的一个方式&#xff0c;阿里云会找我打广告吗&#xff1f;&#xff1f; 这个羊毛不用学生认证&#xff01;&#xff01;只需登录和实名认证和即可 学生实名认证是送优惠券300&#xff0c;我没用上&#xff0c;…

Google 开发者大会(北京站) Play政策会议内容解读

2024年Google开发者大会的会议已结束&#xff0c;很庆幸自己参与了 Google Play 专场&#xff1a;全球视野&#xff0c;助力出海创新与增长 [13:00 - 15:10] 的工作坊内容&#xff0c;受益匪浅。 Google对未来Play市场的愿景&#xff0c;Play Console后台的全新功能&#xff0…

漏洞复现-XXL-JOB accessToken 存在身份认证绕过漏洞

1.漏洞描述 XXL-JOB是许雪里&#xff08;XXL-JOB&#xff09;社区的一款基于java语言的分布式任务调度平台。 2.影响版本 XXL-JOB < 2.2.0 3.影响范围 4.漏洞分析 首先通过微步的漏洞通报说是 src/main/resources/application.properties 默认情况下是非空的&#xff…

HAPropy全功能详解

在一个lvs的环境中&#xff0c;如果服务器出现故障&#xff0c;按照lvs的策略却依然会将访问方式到故障服务器&#xff0c;必然是没有回应的结果&#xff0c;在一个集群中&#xff0c;一台服务器出现故障&#xff0c;理应灵活的去寻找没有故障的服务器&#xff0c;这种方法可以…