【Linux】简单的网络计算器的实现(自定义协议,序列化,反序列化)

news2025/3/13 22:14:28

文章目录

  • 前言
  • 一、 服务端
    • 1.ServerCal.cc(服务器主文件)
    • 2.ServerCal.hpp
    • 3.Sock.hpp(套接字封装)
    • 4.TcpServer.hpp(服务器)
    • 5.Protocol(自定义协议)
  • 二、用户端
    • 1.ClientCal
  • 三、Log.hpp(日志)
  • 四、makefile


前言

我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端`

一、 服务端

在这里插入图片描述

1.ServerCal.cc(服务器主文件)

 #include "TcpServer.hpp"
#include "ServerCal.hpp"
#include <unistd.h>
#include "Daemon.hpp"


using namespace std;
static void Usage(const string &proc){
    cout<<"\nUsage: "<<proc<<" port\n"<<endl;
}

int main(int argc,char*argv[]){
    if(argc!=2){
        Usage(argv[0]);
        exit(0);
    }
    uint16_t port=stoi(argv[1]);
    ServerCal cal;
    TcpServer*tsvp=new TcpServer(port,bind(&ServerCal::Calculator,&cal,placeholders::_1));
    //绑定服务器的回调函数
    tsvp->InitServer();//初始化服务器
    Daemon();//守护进程化
    tsvp->Start();//启动

    return 0;
}

2.ServerCal.hpp

#pragma once
#include <iostream>
#include "Protocol.hpp"

enum
{
    Div_Zero = 1,
    Mod_Zero,
    Other_Oper
};

class ServerCal
{
public:
    ServerCal()
    {
    }
    Response CalculatorHelper(const Request &req)
    {
        Response resp(0, 0);
        switch (req.op)
        {
        case '+':
            resp.result = req.x + req.y;
            break;
        case '-':
            resp.result = req.x - req.y;
            break;
        case '*':
            resp.result = req.x * req.y;
            break;
        case '/':
        {
            if (req.y == 0)
                resp.code = Div_Zero;
            else
                resp.result = req.x / req.y;
        }
        break;
        case '%':
        {
            if (req.y == 0)
                resp.code = Mod_Zero;
            else
                resp.result = req.x % req.y;
        }
        break;
        default:
            resp.code = Other_Oper;
            break;
        }

        return resp;
    }


    // 传入的package形式:"len"\n"10 + 20"\n
    std::string Calculator(std::string &package)
    {
        std::string content;
        //Decode:去掉字符大小len和\n
        bool r = Decode(package, &content); 
        // "len"\n"10 + 20"\n
        if (!r)
            return "";
        // Decode后:"10 + 20"

        Request req;
        r = req.Deserialize(content); 
        // 反序列化过程:"10 + 20" ->x=10 op=+ y=20
        if (!r)
            return "";

        content = "";                        
        //进行计算  
        Response resp = CalculatorHelper(req); // result=30 code=0;

        //result与code进行序列化放入content
        resp.Serialize(&content);  // "30 0"
        
        //Encode:序列化后加上len与\n
        content = Encode(content); // "len"\n"30 0"

        return content;
    }
    ~ServerCal()
    {
    }
};

3.Sock.hpp(套接字封装)

#pragma once

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"

enum
{
    SocketErr = 2,
    BindErr,
    ListenErr
};

const int backlog = 10;

class Sock
{
public:
    Sock() {}
    ~Sock()
    {
    }

public:
    void Socket()
    {
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        {
            lg(Fatal, "socker error,%s: %d", strerror(errno), errno);
            exit(SocketErr);
        }
    }

    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port);
        local.sin_addr.s_addr = INADDR_ANY;

        if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error ,%s: %d", strerror(errno), errno);
            exit(BindErr);
        }

         
    }

    void Listen()
    {
        if (listen(sockfd_, backlog) < 0)
        {
            lg(Fatal, "listen error,%s: %d", strerror(errno), errno);
            exit(ListenErr);
        }
    }

    int Accept(string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(sockfd_, (struct sockaddr *)&peer, &len);
        if (newfd < 0)
        {
            lg(Warning, "accept error,%s: %d", strerror(errno), errno);
            return -1;
        }
        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
        *clientip = ipstr;
        *clientport = ntohs(peer.sin_port);

        return newfd;
    }

    bool Connect(const string &ip, const uint16_t port)
    {
        struct sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr));

        int n = connect(sockfd_, (struct sockaddr *)&peer, sizeof(peer));
        if (n == -1)
        {
            std::cerr << "connect to " << ip << ":" << port << " error" << std::endl;
            return false;
        }
        return true;
    }

    void Close()
    {
        close(sockfd_);
    }

    int Fd()
    {
        return sockfd_;
    }

private:
    int sockfd_;
};

4.TcpServer.hpp(服务器)

#pragma once
#include <functional>
#include <string>
#include <signal.h>
#include "Log.hpp"
#include "Socket.hpp"

using func_t = std::function<std::string(std::string &package)>;

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t callback) : port_(port), callback_(callback)
    {
    }
    bool InitServer()
    {
        listensock_.Socket();
        listensock_.Bind(port_);
        listensock_.Listen();
        lg(Info, "init server .... done");
        return true;
    }


    void Start()
    {
        signal(SIGCHLD, SIG_IGN);
        //防止子进程僵尸,子进程执行完PCB自动释放
        signal(SIGPIPE, SIG_IGN);
        //程序将忽略 SIGPIPE 信号,这意味着即使发生了向已关闭的管道或者 socket 
        //连接中写数据的情况,程序也不会因为收到 SIGPIPE 而终止。
        //这样可以让程序继续正常执行,而不受这种情况的影响。
        while (true)
        {
            std::string clientip;
            uint16_t clientport;
            int sockfd = listensock_.Accept(&clientip, &clientport);
            if (sockfd < 0)
                continue;
            lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);

            // 子进程提供服务
            if (fork() == 0)
            {
                listensock_.Close();
                std::string inbuffer_stream;
                // 数据计算
                while (true)
                {
                    //读到客户端发送的报文
                    char buffer[1280];
                    ssize_t n = read(sockfd, buffer, sizeof(buffer));
                    if (n > 0)
                    {   
                        //说明读取报文成功
                        buffer[n] = 0;
                        //每次都加上读取的数据是因为有可能我的一个完整报文分两次读取
                        //加上第二次的才能算上一个完整的报文
                        inbuffer_stream += buffer;

                        lg(Debug, "debug:\n%s", inbuffer_stream.c_str());

                        while (true)
                        {   
                            std::string info = callback_(inbuffer_stream);
                            //调用string Calculator(std::string &package)

                            if (info.empty())//没有读出说明数据处理失败
                                break;
                            lg(Debug, "debug, response:\n%s", info.c_str());
                            lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
                            //write写入处理好后的计算过的数据(经过了序列化)
                            write(sockfd, info.c_str(), info.size());
                        }
                    }
                    else if (n == 0)
                        break;
                    else
                        break;
                }

                exit(0);
            }
            //父进程关闭多余的文件描述符
            close(sockfd);
        }
    }
    ~TcpServer()
    {
    }

private:
    uint16_t port_;
    Sock listensock_;
    func_t callback_;
};

5.Protocol(自定义协议)

#pragma once

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
 

const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";


//编码:给序列化后的字符串加上字符长度与\n
std::string Encode(std::string &content)
{
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;
// "len"\n"x op y"\nXXXXXX
    return package;
}

 
// "protocolnumber"\n"len"\n"x op y"\nXXXXXX
//目的:截取字符串“x op y”放入content中,并把与此字符串
//相关的数据从总报文package中移除
bool Decode(std::string &package, std::string *content)
{
    
    std::size_t pos = package.find(protocol_sep);
    if(pos == std::string::npos) return false;

    //len_str:len的字符串形式
    std::string len_str = package.substr(0, pos);
    std::size_t len = std::stoi(len_str);
    // package = len_str + content_str + 2
    //
    std::size_t total_len = len_str.size() + len + 2;
    if(package.size() < total_len) return false;

    *content = package.substr(pos+1, len);
    // earse 移除报文 package.erase(0, total_len);
    package.erase(0, total_len);

    return true;
}


// json, protobuf
class Request
{
public:
    Request(int data1, int data2, char oper) : x(data1), y(data2), op(oper)
    {
    }
    Request()
    {}
public:
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // 构建报文的有效载荷
        // struct => string, "x op y"
        std::string s = std::to_string(x);
        s += blank_space_sep;
        s += op;
        s += blank_space_sep;
        s += std::to_string(y);
        *out = s;
        return true;
#else
        Json::Value root;
        root["x"] = x;
        root["y"] = y;
        root["op"] = op;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) 
    // 目的:从"x op y"中分离出x=?,y=?,op=?填入类的成员变量
    {
#ifdef MySelf
        std::size_t left = in.find(blank_space_sep);
        if (left == std::string::npos)
            return false;
        std::string part_x = in.substr(0, left);

        std::size_t right = in.rfind(blank_space_sep);
        if (right == std::string::npos)
            return false;
        std::string part_y = in.substr(right + 1);

        if (left + 2 != right)
            return false;
        op = in[left + 1];
        x = std::stoi(part_x);
        y = std::stoi(part_y);
        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        x = root["x"].asInt();
        y = root["y"].asInt();
        op = root["op"].asInt();
        return true;
#endif
    }
    void DebugPrint()
    {
        std::cout << "新请求构建完成:  " << x << op << y << "=?" << std::endl;
    }
public:
    // x op y
    int x;
    int y;
    char op; // + - * / %
};

class Response
{
public:
    Response(int res, int c) : result(res), code(c)
    {
    }

    Response()
    {}
public:
    bool Serialize(std::string *out)
    {
#ifdef MySelf
        // "result code"
        // 构建报文的有效载荷
        std::string s = std::to_string(result);
        s += blank_space_sep;
        s += std::to_string(code);
        *out = s;
        return true;
#else
        Json::Value root;
        root["result"] = result;
        root["code"] = code;
        // Json::FastWriter w;
        Json::StyledWriter w;
        *out = w.write(root);
        return true;
#endif
    }
    bool Deserialize(const std::string &in) // "result code"
    {
#ifdef MySelf
        std::size_t pos = in.find(blank_space_sep);
        if (pos == std::string::npos)
            return false;
        std::string part_left = in.substr(0, pos);
        std::string part_right = in.substr(pos+1);

        result = std::stoi(part_left);
        code = std::stoi(part_right);

        return true;
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        result = root["result"].asInt();
        code = root["code"].asInt();
        return true;
#endif

    }
    void DebugPrint()
    {
        std::cout << "结果响应完成, result: " << result << ", code: "<< code << std::endl;
    }
public:
    int result;
    int code; // 0,可信,否则!0具体是几,表明对应的错误原因
};

二、用户端

1.ClientCal

#include <iostream>
#include <string>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"

static void Usage(const std::string &proc)
{
    std::cout << "\nUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./clientcal ip port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    Sock sockfd;
    sockfd.Socket();
    bool r = sockfd.Connect(serverip, serverport);
    if(!r) return 1;

    srand(time(nullptr) ^ getpid());
    int cnt = 1;
    const std::string opers = "+-*/%=-=&^";

    std::string inbuffer_stream;
    while(cnt <= 10)
    {
        std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl;
        int x = rand() % 100 + 1;
        usleep(1234);
        int y = rand() % 100;
        usleep(4321);
        char oper = opers[rand()%opers.size()];
        Request req(x, y, oper);//构造需求
        req.DebugPrint();

        std::string package;
        req.Serialize(&package);

        package = Encode(package);

        //经过序列化传给服务器
        write(sockfd.Fd(), package.c_str(), package.size());
        

        char buffer[128];
        ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); // 我们也无法保证我们能读到一个完整的报文
        if(n > 0)
        {   
            //读取服务器处理后的报文
            buffer[n] = 0;
            inbuffer_stream += buffer; // "len"\n"result code"\n
            std::cout << inbuffer_stream << std::endl;
            std::string content;
            bool r = Decode(inbuffer_stream, &content);  
            // 目的:得到字符串"result code"
            assert(r);

            Response resp;
            。。反序列化
            r = resp.Deserialize(content);
            assert(r);

            resp.DebugPrint();
        }

        std::cout << "=================================================" << std::endl;
        sleep(1);

        cnt++;
    }


    sockfd.Close();
    return 0;
}

三、Log.hpp(日志)

详细可参考我之前写的博客【Linux】记录错误信息日志的实现

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

using namespace std;

#define SIZE 1024

#define Info 0
#define Debug 1
#define Warning 2
#define Error 3 
#define Fatal 4

#define Screen 1
#define Onefile 2
#define ClassFile 3

#define LogFile "log.txt"

class Log{
public:
Log(){
    printMethod=Screen;
    path="./log/";
}

void Enable(int method){
    printMethod=method;
}

 string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

void printLog(int level,const string&logtxt){
    switch(printMethod){
        case Screen:
            cout<<logtxt<<endl;
            break;
        case Onefile:
            printOneFile(LogFile,logtxt);
            break;
        case ClassFile:
            printClassFile(level,logtxt);
            break;
        default:
            break;
    }
}

void printOneFile(const string&logname,const string&logtxt){
    string _logname=path+logname;
    int fd=open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
    if(fd<0){
        return ;
    }
    write(fd,logtxt.c_str(),logtxt.size());
    close(fd);
}

void printClassFile(int level,const string&logtxt){
    string filename=LogFile;
    filename+=".";
    filename+=levelToString(level);
    printOneFile(filename,logtxt);
}

~Log(){

}

 void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    string path;
};

Log lg;

四、makefile

.PHONY:all
all:servercal clientcal

Flag=#-DMySelf=1
Lib=-ljsoncpp

servercal:ServerCal.cc
	g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)
clientcal:ClientCal.cc
	g++ -o $@ $^ -std=c++11 $(Lib) $(Flag)


.PHONY:clean
clean:
	rm -f clientcal servercal

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

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

相关文章

宜昌博物馆龙文物展,以数据为盾完成文物保护

​一、湖北宜昌博物馆龙文物精品展的独特魅力 近日&#xff0c;在湖北宜昌博物馆举行的甲辰年龙文物精品展上&#xff0c;多件包含“龙元素”的文物正式向社会展出。龙自古以来就是中华民族象征&#xff0c;带有“龙图案”或“龙元素”的物件&#xff0c;广泛存在于中国人“吃…

JVM-JVM调优基础(理论)

申明&#xff1a;文章内容是本人学习极客时间课程所写&#xff0c;作为笔记进行记录&#xff0c;文字和图片基本来源于课程资料&#xff0c;在某些地方会插入一点自己的理解&#xff0c;未用于商业用途&#xff0c;侵删。 原资料地址&#xff1a;课程资料 JVM参数 标准参数 …

人工智能学习与实训笔记(十五):Scikit-learn库的基础与使用

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 一、介绍 1. 1 Scikit-learn的发展历程及定义 1.2 理解算法包、算法库及算法框架之间的区别和联系 二、Scikit-learn官网结构 三、安装与设置 3.1 Python环境的安装与配置 3.2 Scikit-lea…

如何实现批量获取电商数据自动化商品采集?如何利用电商数据API实现业务增长?

随着电子商务的快速发展&#xff0c;数据已经成为了电商行业最重要的资产之一。在这个数据驱动的时代&#xff0c;电商数据API&#xff08;应用程序接口&#xff09;的作用日益凸显。通过电商数据API&#xff0c;商家能够获取到大量关于消费者行为、产品表现、市场趋势等有价值…

模拟电子技术——基本微积分运算电路、滤波器、一阶低通滤波器、一阶高通滤波器、电压比较器、滞回比较器

文章目录 一、基本微积分运算电路什么是微积分电路电路及特点及参数计算-积分电路电路及特点及参数计算-微分电路 二、滤波器什么是滤波器滤波器分类有源无源的电路结构区别通带与阻带参数 三、一阶低通滤波器什么是一阶低通滤波器低通滤波器特性电路及相关特性 四、一阶高通滤…

【笔记------STM32】MX_RTC_Init()初始化RTC时RTC_ISR_INITF位超时失败的解决方法

RTC和flash有点像&#xff0c;有些功能需要解锁才能配置&#xff0c;虽然cubeMX生成的RTC部分的解锁配置正确&#xff0c;但却没有配置好前提条件&#xff1a;关闭PWR模块的备份域写保护使能&#xff0c;有点奇怪&#xff0c;手动关掉就好了 现象&#xff1a;进入RTC_EnterInit…

打开ps显示找不到dll怎么办?这四种方法可快速修复

在计算机操作系统中&#xff0c;当执行某程序或运行特定软件时&#xff0c;如果系统提示“ps显示找不到dll文件”&#xff0c;这其实是一个较为常见的问题现象。动态链接库&#xff08;DLL&#xff09;文件是Windows操作系统中不可或缺的重要组件&#xff0c;它包含了大量可被多…

Spring Boot 笔记 024 登录页面

1.1 登录接口 //导入request.js请求工具 import request from /utils/request.js//提供调用注册接口的函数 export const userRegisterService (registerData)>{//借助于UrlSearchParams完成传递const params new URLSearchParams()for(let key in registerData){params.a…

【Git】 大厂代码提交原则(适用新手,细节拉满)

目录 git是什么&#xff0c;为什么要学git 开发目录 提交到github/gitee的命令 提交代码时的注意事项 git 的其他命令 git是什么&#xff0c;为什么要学git git 是一个免费的、开源的分布式版本控制系统&#xff0c;git可以追踪文件的修改历史&#xff0c;并且在不同的人员…

Sora 文生视频提示词实例集 2

Prompt: Historical footage of California during the gold rush. 加利福尼亚淘金热期间的历史影像。 Prompt: A close up view of a glass sphere that has a zen garden within it. There is a small dwarf in the sphere who is raking the zen garden and creating patter…

Vue+Vite项目初建(axios+Unocss+iconify)

一. 创建项目 npx --package vue/cli vue 项目成功启动后&#xff0c;进入http://localhost:3200&#xff0c;即可进入创建好的页面(假设启动端口为3200) 二. 测试网络通讯模块 假设有本地服务器地址localhost:8000提供接口服务&#xff0c;接口为localhost:8000/token&#…

CCF-A类VLDB’24 3月1日截稿!数据界的璀璨盛会等你投稿!

会议之眼 快讯 第50届VLDB( International Conference on Very Large Databases)即超大型数据库国际会议将于 2024 年 8月25日至29日在中国广州朗廷广场隆重举行&#xff01;VLDB大会是数据库领域的顶级学术盛会&#xff0c;而SIGMOD和ICDE则是与之齐名的另外两大数据库会议。这…

天锐绿盾 | 办公终端文件数据\资料防泄密软件

天锐绿盾是一款电脑文件防泄密软件&#xff0c;旨在帮助企业保护其敏感数据免受未经授权的访问和泄露。该软件采用先进的加密技术和文件监控机制&#xff0c;确保企业数据在存储、传输和使用过程中的安全性。 PC地址&#xff1a;https://isite.baidu.com/site/wjz012xr/2eae091…

Docker打包离线镜像到本地,上传解压到服务器

在我们部署Docker镜像时&#xff0c;难免会遇到服务器没有网络情况&#xff0c;Centos7离线安装Docker 这篇文章中&#xff0c;我们已经离线安装Docker完成&#xff0c;现在需要离线创建容器。 1 查看所有镜像 docker images注&#xff1a;如果我们想打包本服务器没有的镜像&…

v62.数组

1.简单介绍 就像数据可以存放在变量中&#xff0c;每个变量有名字、类型、以及内存空间。还可以用数组来保存 相同数据类型 的数据。 那么就避免了使用很多个独立的变量。数组是长度固定的数据结构&#xff0c;用来存放指定类型的数据。 2.初试数组 int num1,num2,num3...类…

第三百五十二回

文章目录 1. 概念介绍2. 获取方法3. 示例代码4. 对比与总结4.1 横向对比4.2 内容总结 我们在上一章回中介绍了"如何获取当前系统语言"相关的内容&#xff0c;本章回中将介绍如何获取当前时区.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们使用的…

09_Java集合

一、Java集合框架概述 一方面&#xff0c; 面向对象语言对事物的体现都是以对象的形式&#xff0c;为了方便对多个对象的操作&#xff0c;就要对对象进行存储。另一方面&#xff0c;使用Array存储对象方面具有一些弊端&#xff0c;而Java 集合就像一种容器&#xff0c;可以动态…

城市管网可视化泄露检测系统

行业背景 目前传统的漏水检测模式存在的缺陷主要体现在以下几方面&#xff1a; 首先是检测灵敏度低&#xff0c;且不稳定。听音检测主要依赖人对声音的强度和频率的感觉&#xff0c;只能是定性的&#xff0c;且不同人的听觉能力不同&#xff0c;相同人在不同时间听觉效果也不…

芋道---实现可退回至申请人节点(附完整代码)

现有如下需求&#xff0c;审批人在退回申请时&#xff0c;想退回至申请人节点&#xff0c;但目前芋道并不支持退回至申请人节点&#xff0c;现做如下修改&#xff0c;实现该需求&#xff1a; 步骤一&#xff1a;设计流程模型 首先&#xff0c;我们在设计流程模型时&#xff0c…

Java并发基础:ConcurrentSkipListMap全面解析

内容概要 ConcurrentSkipListMap类它融合了跳表的高效查找与并发控制的稳定性&#xff0c;在多线程环境下&#xff0c;该类提供了出色的线程安全性能&#xff0c;确保数据的一致性与完整性&#xff0c;其操作具有对数级别的时间复杂度&#xff0c;即使在大数据集下也能维持高效…