【Linux】序列化和反序列化

news2024/11/24 8:43:28

目录

  • 🌈前言
  • 🌸1、应用层
  • 🌺2、重谈协议
  • 🍁3、网络计算器
    • 🍡3.1、定制协议
    • 🍢3.2、样例代码

🌈前言

这篇文章给大家带来序列化和反序列化的学习!!!


🌸1、应用层

前言:之前所写的所有网络应用程序服务,比如:英文大小写转换、聊天室都是在应用层上进行编写的!!!

  • 应用层:它是OSI模型中的第七层模块,用于提供基本的网络应用服务,主要功能是在网络上客户端与服务器之间的网络通信,并且服务器向客户端提供基础的应用服务,应用层就是来编写服务的

  • 程序员写的一个个解决我们实际的问题;满足我们日常需求的网络程序,都是在应用层

  • 我们前面在应用层所写的socket代码,都是调用下层的功能(比如使用传输层的TCP或UDP协议进行可靠或不可靠的方式进行数据传输)来辅助应用层来完成特定的定制化服务

  • 应用层常用的协议有:HTTP、HTTPS、Telnet、DNS、FTP协议等等…

img


🌺2、重谈协议

前言:前面我们谈过协议其实是双方之间的约定,这次我们来进行深入的理解

  • 协议是一种约定,那么我们在编写套接字代码时,读写数据都是按字符串的方式来发送和接收的
  • 计算机与网络设备想要互相通信,双边就必须基于相同的方法
  • 如何找到对方,使用什么语言进行通信;不同的硬件、OS之间的通信;都需要规则进行约束,这种规则就是协议
如果我们要传输一些结构化数据该怎么办呢?
  • 方案一:直接将结构体对象按字节进行发送,这样可行吗???
  • 问题一:直接发送结构体对象,是不可取的,因为客户端和服务器的结构体数据内存对齐方式可能不同,比如客户端是windows运行的,服务器是部署在Linux上的,它们的内存对齐方式不同
  • 问题二:客户端的版本号和服务器不同,比如新版本的结构体可能拓展了新的成员变量,如果我们直接发送结构体对象,也会出现异常问题~(数据丢失,数据错误问题)

在这里插入图片描述


序列化和反序列化
  • 这时候,我们就需要定制化协议了,意思是客户端将结构体进行序列化,并且进行编码后发送请求给服务器
  • 服务器拿到请求(报文)后进行解码且拿到有效载荷,并且进行反序列化拿到结构化数据
  • 序列化:将结构化数据转换成字符串
  • 反序列化:将字符串转换成结构化数据
  • 编码:结构化数据转换成字符串后,对字符串进行长度计算,添加到字符串的首部,并且添加特定的长度字段和每个有效载荷之间的分隔符 – 不考虑加密
  • 解码:客户端拿到报文后,根据报文头部包含有效载荷长度的字段,通过分隔符找到长度字段来提取有效载荷 – 不考虑解密
  • 序列化和反序列化是将数据从一种表示形式转换为另一种表示形式的过程,序列化和反序列化操作时,数据格式必须一致,否则会导致数据解析错误或者版本不兼容的问题!!!

在这里插入图片描述


🍁3、网络计算器

🍡3.1、定制协议

前言:因为网络计算器是需要传输结构化数据进行计算的,不是简简单单的英文大小写转换,需要定制协议

约定
  • 客户端发送请求,构建一个请求类,里面包含序列化和反序列化接口
  • 服务器响应请求,构建一个响应类,里面包含序列化和反序列化
  • 编码和解码是双方都要进行使用,因为网络通信是全双工的,双方都能给对方发报文,所以都要编解码!
约定一
  • 首先客户端构建请求类,序列化时,结构体的每个成员变量的分隔符为空格
  • 对序列化后的数据进行编码,编码是将有效载荷(包含空格)的长度字段放到报文首部,并且添加特定的分隔符,长度字段和报文间的分隔符都使用"\r\n"
  • 服务器对报文进行解码时,根据首部长度字段和分隔符提取有效载荷数据
  • 提取到有效载荷后,将有效载荷反序列化为结构化数据
约定二
  • 服务器拿到结构化数据后,通过它来构建响应类,后面又是序列化成字符串,然后进行编码发送报文
  • 客户端拿到响应报文后,对其进行解码(按首部有效载荷长度和分割符提取有效载荷),随后将解码后的数据反序列化到响应类中,最后数据结果即可!

🍢3.2、样例代码

定制协议头文件 – CusPro.hpp

#pragma once

#include <iostream>
#include <string>

#define BUFFER 1024
#define CRLF "\r\n"
#define CRLF_LEN strlen(CRLF)
#define SPACE " "
#define SPACE_LEN 1
#define OPS_ALL "+-*/%"

// makefile控制使用jsoncpp还是定制序列化和反序列化
class Requst
{
public:
    // 序列化 -- 结构化数据 -> 字符串
    void Serialize(std::string &out)
    {
        // "LeftOprand_ op_ RightOprand_"
        std::string x(std::to_string(LeftOprand_));
        std::string y(std::to_string(RightOprand_));
        out = x;
        out += SPACE;
        out += op_;
        out += SPACE;
        out += y;
    }

    // 反序列化 -- 字符串 -> 结构化数据 -> "100 + 200"
    bool Deserialize(std::string &in)
    {
    	// 找空格
        size_t spaceOne = in.find(SPACE);
        size_t spaceTwo = in.rfind(SPACE);
        std::cout << in << std::endl;

        if (spaceOne == std::string::npos)
            return false;

        if (spaceTwo == std::string::npos)
            return false;

		// 截取对应的成员变量,并且转换成对应的成员变量类型赋值给成员变量
        std::string leftData(in.substr(0, spaceOne));
        LeftOprand_ = std::stoi(leftData);

        std::string Op(in.substr(spaceOne + SPACE_LEN, spaceTwo - (spaceOne + SPACE_LEN)));
        if (Op.size() == 1)
            op_ = Op[0];
        else
            return false;

        std::string rightData(in.substr(spaceTwo + SPACE_LEN));
        RightOprand_ = std::stoi(rightData);
        return true;
    }

public:
    int &GetLeftOprand() { return LeftOprand_; }
    int &GetRightOprand() { return RightOprand_; }
    char &GetOp() { return op_; }

private:
    int LeftOprand_;
    char op_;
    int RightOprand_;
};

//-------------------------------------------------------------------------------------------------------

class Response
{
public:
    // 序列化 -- 结构化数据 -> 字符串
    void Serialize(std::string &out)
    {
        // "exitcode_ result_"
        std::string exitcode(std::to_string(exitcode_));
        std::string result(std::to_string(result_));
        out = exitcode;
        out += SPACE;
        out += result;
    }

    // 反序列化 -- 字符串 -> 结构化数据
    bool Deserialize(std::string &in)
    {
        // "exitcode_ result_"
        int Space_Index = in.find(SPACE);
        if (Space_Index == std::string::npos)
            return false;

        std::string exitcode(in.substr(0, Space_Index));
        std::string result(in.substr(Space_Index + SPACE_LEN));

        exitcode_ = std::stoi(exitcode);
        result_ = std::stoi(result);
    }

public:
    int &GetResult() { return result_; }
    int &GetExitcode() { return exitcode_; }

private:
    int exitcode_ = 0; // 错误码,!0为异常 -- 除/模零错误...
    int result_;   // 计算结果
};

//-------------------------------------------------------------------------------------------------------

// 编码 -- 序列化后添加有效载荷的长度和分隔符
std::string enCoded(std::string &in, uint32_t len)
{
    // "len\r\nxxx xxx xxx\r\n" -- "5\r\n1 + 1\r\n"
    std::string package(std::to_string(len));
    package += CRLF;
    package += in;
    package += CRLF;
    return package;
}

// 解码 -- 根据编码的长度获取有效载荷 -- "9\r\n100 + 200\r\n"
std::string Decode(std::string &in, uint32_t *len)
{
    // 1. 判断是否包含len
    *len = 0;
    size_t pos = in.find(CRLF);
    if (pos == std::string::npos)
        return "";

    // 2. 提取长度
    std::string inLenStr = in.substr(0, pos);
    int inLen = std::atoi(inLenStr.c_str());

    // 3. 判断有效数据长度是否符合
    int DataLen = in.size() - (2 * CRLF_LEN) - pos;
    if (DataLen < inLen)
        return "";

    // 4. 提取完整的数据
    std::string package(in.substr(pos + CRLF_LEN, inLen));
    *len = inLen;

    // 5. 将当前报文从in中完整的移除(特殊情况,Server可能读多数据)
    int removeLen = inLenStr.size() + (2 * CRLF_LEN) + package.size();
    in.erase(0, removeLen);

    return package;
}

//-------------------------------------------------------------------------------------------------------

// "1 +     1"; "1+ 1"; "1     +1" --> "1+1" -- 数据清洗(去除空格(SPACE))
void DataClean(std::string& outbuffer)
{
    int strLen = outbuffer.size();
    int j = 0;
    for (int i = 0; i < strLen; ++i)
    {
        if (outbuffer[i] != ' ')
        {
            outbuffer[j] = outbuffer[i];
            ++j;
        }
    }
    outbuffer = outbuffer.substr(0, j);
    std::cout << "debug: " << outbuffer << std::endl;
}

// 构建请求类
bool makeRequst(const std::string &in, Requst &req)
{
    // Client不同的输入格式: "123+1" -- "123-1"....
    char strtmp[BUFFER];
    snprintf(strtmp, BUFFER, "%s", in.c_str());

    char *leftOperand = strtok(strtmp, OPS_ALL);
    if (leftOperand == nullptr)
        return false;

    char *rightOperand = strtok(nullptr, OPS_ALL);
    if (rightOperand == nullptr)
        return false;

    char Op = in[strlen(leftOperand)];
    req.GetLeftOprand() = std::stoi(leftOperand);
    req.GetOp() = Op;
    req.GetRightOprand() = std::stoi(rightOperand);
    return true;
}

Util.hpp – 工具头文件,用于保存重复的头文件

#pragma once

#include <iostream>
#include <thread>
#include <string>
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

const int BUFFER_SIZE  = 1024;
#define FORMAT_ERR 1
#define SER_SOCKET 2
#define BIND_ERR   3
#define LISTEN_ERR 4
#define ACCEPT_ERR 5
#define WRITE_ERR  6
#define CLISOCK_ERR 7
#define CONNECT_ERR 8

makefile

.PHONY:all
all:server client

server:TcpServer.cc
	g++ -o $@ $^ -std=c++11 -pthread

client:TcpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf server client

TcpServer.cc – 服务器

#include "Util.hpp"
#include "CusPro.hpp"

class ClientData
{
public:
    ClientData()
    {
    }

private:
    int ClientSockfd_;
    std::string ClientIP_;
    uint16_t ClientPort_;
};

class TcpServer
{
public:
    TcpServer(uint16_t Port, std::string IP)
        : sockfd_(-1), IP_(IP), Port_(Port)
    {
    }

    void Init()
    {
        // 获取套接字资源
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        {
            std::cout << "Get Socket error: " << strerror(errno) << std::endl;
            exit(SER_SOCKET);
        }

        // 绑定网络信息
        sockaddr_in ServerInDa;
        socklen_t len = sizeof(ServerInDa);
        memset(&ServerInDa, 0, len);
        ServerInDa.sin_family = AF_INET;
        ServerInDa.sin_port = htons(Port_);
        ServerInDa.sin_addr.s_addr = (IP_.empty() ? htonl(INADDR_ANY) : inet_addr(IP_.c_str()));
        if (bind(sockfd_, (const sockaddr *)&ServerInDa, len) < 0)
        {
            std::cout << "Bind error: " << strerror(errno) << std::endl;
            exit(BIND_ERR);
        }

        // 设置监听套接字
        if (listen(sockfd_, SOMAXCONN) < 0)
        {
            std::cout << "listen error: " << strerror(errno) << std::endl;
            exit(LISTEN_ERR);
        }
    }

    void Start()
    {
        int SerSockfd;
        while (true)
        {
            // 获取用户连接
            sockaddr_in ClientInDa;
            socklen_t len = sizeof(ClientInDa);
            memset(&ClientInDa, 0, len);
            SerSockfd = accept(sockfd_, (sockaddr *)&ClientInDa, &len);
            if (SerSockfd < 0)
            {
                std::cout << "accept error: " << strerror(errno) << std::endl;
                exit(ACCEPT_ERR);
            }

            // 保存Client Network Data -- 网络序列->主机序列
            std::string ClientIp = inet_ntoa(ClientInDa.sin_addr);
            uint16_t ClientPort = ntohs(ClientInDa.sin_port);

            std::cout << "用户[" << ClientIp << ":" << ClientPort
                      << "]连接Server sucess!!!" << std::endl;
            //----------------------------------------------------------------------------------------------
            // 多线程版本
            RunThread(ClientIp, ClientPort, SerSockfd);
        }
    }

    void RunThread(std::string &ClientIp, uint16_t ClientPort, int SerSockfd)
    {
    	// 定义C++标准库的线程类,因为线程只能调用全局和静态的函数,这里调用成员函数,所以这里必须要传this
        std::thread Thread(&TcpServer::netCal, this, std::ref(ClientIp), ClientPort, SerSockfd);
        Thread.detach();
    }

    //----------------------------------------------------------------------------------------------------
public:
    // 网络计算器服务 -- 包含定制协议内容
    void netCal(std::string &ClientIp, uint16_t ClientPort, int SerSockfd)
    {
        std::string inbuffer;
        char buffer[BUFFER_SIZE];
        // 因为TCP传输机制,可能会只传一部分,需要循环读取Data
        while (true)
        {
            memset(buffer, 0, BUFFER_SIZE);
            // 1. 读取数据
            ssize_t s = read(SerSockfd, buffer, BUFFER_SIZE - 1);
            if (s == 0)
            {
                std::cout << "Client write close!!!" << std::endl;
                break;
            }
            else if (s < 0)
            {
                std::cout << "Server read error: " << strerror(errno) << std::endl;
                break;
            }
            buffer[s] = '\0';
            inbuffer += buffer;

            // 2. 判断有效载荷是否完整 -- Client -> Data: "len\r\nxxxxxxxxxx\r\n"
            Requst req;
            uint32_t packagLen = 0;
            std::string package = Decode(inbuffer, &packagLen);
            if (packagLen == 0)
                continue; // 提取完整有效载荷失败!!!
            
            // 3. 构建响应类 -> 序列化 -> 编码 -> write Client
            if (req.Deserialize(package))
            {
                Response resp = calculator(req);
                std::string respackage;
                resp.Serialize(respackage);
                respackage = enCoded(respackage, respackage.size());
                
                ssize_t w = write(SerSockfd, (void*)respackage.c_str(), respackage.size());
                if (write < 0)
                {
                    std::cout << "Server write to Client error -> " << strerror(errno) << std::endl;
                    break;
                }
            }
        }
    }

private:
    Response calculator(Requst& req)
    {
        Response resp;
        switch (req.GetOp())
        {
            case '+':
                resp.GetResult() = req.GetLeftOprand() + req.GetRightOprand();
            break;
            case '-':
                resp.GetResult() = req.GetLeftOprand() - req.GetRightOprand();
            break;
            case '*':
                resp.GetResult() = req.GetLeftOprand() * req.GetRightOprand();
            break;
            case '/':
            {
                if (req.GetRightOprand() == 0)
                {
                    resp.GetExitcode() = -1; // 除零错误
                }
                else
                {
                    resp.GetResult() = req.GetLeftOprand() / req.GetRightOprand();
                }
            }
            break;
            case '%':
            {
                if (req.GetRightOprand() == 0)
                {
                    resp.GetExitcode() = -2; // 模零错误
                }
                else
                {
                    resp.GetResult() = req.GetLeftOprand() % req.GetRightOprand();
                }
            }
            break;
            default:
                resp.GetExitcode() = -3; // 非法操作
                std::cout << "Please enter the correct format: xx [+-*/%] xx" << std::endl;
            break;
        }
        return resp;
    }

    //-----------------------------------------------------------------------------------

private:
    int sockfd_;
    std::string IP_;
    uint16_t Port_;
};

int main(int argc, char *argv[])
{
    // 命令行参数格式: [可执行程序] [IP地址(可以不传)] [port]
    if (argc < 2)
    {
        std::cout << "Instruction format: [可执行程序] [IP地址(可以不传)] [port]" << std::endl;
        exit(FORMAT_ERR);
    }
    std::string IP;
    uint16_t Port;
    if (argc > 2)
    {
        IP = argv[1];
        Port = std::atoi(argv[2]);
    }
    else
    {
        Port = std::atoi(argv[1]);
    }

    // 启动服务器
    TcpServer tps(Port, IP);
    tps.Init();
    tps.Start();
    return 0;
}

TcpClient.cc – 客户端

#include "Util.hpp"
#include "CusPro.hpp"

class TcpClient
{
public:
    TcpClient(std::string ServerIP, uint16_t Port)
        : ServerIP_(ServerIP), Port_(Port)
    {
    }

    ~TcpClient()
    {
        if (sockfd_ > 2)
            close(sockfd_);
    }

    void Init()
    {
        // 获取套接字资源
        sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd_ < 0)
        {
            std::cout << "Get Socket error: " << strerror(errno) << std::endl;
            exit(CLISOCK_ERR);
        }

        // 填充Server inetwork Data
        sockaddr_in ServerInDa;
        socklen_t len = sizeof(ServerInDa);
        memset(&ServerInDa, 0, len);
        ServerInDa.sin_family = AF_INET;
        ServerInDa.sin_port = htons(Port_);
        ServerInDa.sin_addr.s_addr = inet_addr(ServerIP_.c_str());

        // 向指定Server发送连接请求 -- connect自动绑定网络信息(ip with port) to 内核
        if (connect(sockfd_, (const sockaddr *)&ServerInDa, len) < 0)
        {
            std::cout << "Connect Server error: " << strerror(errno) << std::endl;
            exit(CONNECT_ERR);
        }
    }

    void Start()
    {
        std::string outbuffer;
        char inbuffer[BUFFER_SIZE];
        while (true)
        {
            // 发送请求
            std::cout << "Client echo# ";
            fflush(stdout);
            std::getline(std::cin, outbuffer);

            if (strcasecmp(outbuffer.c_str(), "quit") == 0)
            {
                std::cout << "Client exit!!!" << std::endl;
                exit(0);
            }

            // -------------------------------------------------------------------------------------------

            // 请求 -> 构建请求类 -> 序列化 -> 编码
            DataClean(outbuffer); // 数据清洗 -- 去掉空格
            Requst req;
            if (!(makeRequst(outbuffer, req))) continue;
            std::string package;
            req.Serialize(package);
            std::cout << package << std::endl;
            package = enCoded(package, package.size());
            std::cout << package << std::endl;


            // -------------------------------------------------------------------------------------------

            ssize_t Write = write(sockfd_, (void *)package.c_str(), package.size());
            if (Write < 0)
            {
                std::cout << "Client write data error" << strerror(errno) << std::endl;
                continue;
            }

            // 获取结果
            ssize_t Read = read(sockfd_, inbuffer, sizeof(inbuffer) - 1);
            if (Read > 0)
            {
                inbuffer[Read] = '\0';

            // -------------------------------------------------------------------------------------------`
                
                // 解码 -> 定义响应类 -> 将解码的str反序列化到响应类
                std::string enPackage(inbuffer);
                uint32_t len = 0;
                enPackage = Decode(enPackage, &len);
                if (len < 0)
                    continue;
                
                Response resp;
                resp.Deserialize(enPackage);
                std::cout << "result: " << resp.GetResult() << ", exitcode: " << resp.GetExitcode() << std::endl;

            // -------------------------------------------------------------------------------------------

                // std::cout << "Client response result echo# " << inbuffer << std::endl;
            }
            else if (Read == 0)
            {
                std::cout << "Server read close, stop service!!!" << std::endl;
                break;
            }
            else
            {
                std::cout << "Read result fail: " << strerror(errno) << std::endl;
                continue;
            }
            memset(inbuffer, 0, BUFFER_SIZE);
        }
    }

private:
    int sockfd_;
    std::string ServerIP_;
    uint16_t Port_;
};

int main(int argc, char *argv[])
{
    // [可执行程序] [IP adress] [port]
    if (argc < 3)
    {
        std::cout << "Instruction format: [可执行程序] [IP地址] [port]" << std::endl;
        exit(FORMAT_ERR);
    }
    std::string IP = argv[1];
    uint16_t port = std::atoi(argv[2]);

    TcpClient tpc(IP, port);
    tpc.Init();
    tpc.Start();
    return 0;
}

总结:

  • 在进行序列化和反序列化操作时,必须约定号数据格式和版本号,并且确保发送方和接收方使用相同的格式和版本号进行数据传输!

  • 在进行序列化和反序列化操作时,还需要注意内存对齐方式和字节序的问题,因为不同平台之间的字节序可能不一致,还要就是结构体对其在不同平台也可能不同!

  • 序列化后的数据大小可能会比原始数据要大,需要注意大小的问题,避免数据过大而导致网络传输效率低下或存储空间不足的问题!

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

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

相关文章

chatgpt赋能python:Python倒序for:如何优化循环遍历

Python倒序for&#xff1a;如何优化循环遍历 Python作为一门高级编程语言&#xff0c;一直以来都在开发者中得到广泛的应用。在日常的编程工作中&#xff0c;遍历列表&#xff08;List&#xff09;和元组&#xff08;Tuple&#xff09;是很常见的操作。而对于列表或元组的倒序…

NLP(五十四)tiktoken的使用

tiktoken是OpenAI于近期开源的Python第三方模块&#xff0c;该模块主要实现了tokenizer的BPE&#xff08;Byte pair encoding&#xff09;算法&#xff0c;并对运行性能做了极大的优化。本文将介绍tiktoken模块的使用。 tiktoken简介 BPE(Byte pair encoding)算法是NLP中常见的…

Redis系列---Redis网络模型1

我们都知道&#xff0c;redis的高性能是具有多方面的因数&#xff0c;如&#xff1a;运行在内存上&#xff0c;单线程命令&#xff0c;io多路复用技术等&#xff0c;对于redis高性能的探究&#xff0c;就需要深入的研究其工作原理&#xff0c;这就涉及到redis的网络模型了&…

python实现单链表、双链表、反转链表(二)

一、链表概述 链表是有元素组成的数据结构&#xff0c;每个元素都是单独对象&#xff0c;包含数据和指针信息 链表中的每个元素称为节点&#xff0c;如下所示&#xff0c;第一个节点称为Head(头节点)&#xff0c;为链表的入口点&#xff0c;如果链表为空&#xff0c;则Head指…

Vue.js 比较重要知识点总结二

概述 vue3 组合式API生命周期钩子函数有变化吗&#xff1f;Composition API 与 Options API 有什么区别&#xff1f;watch 和 watchEffect 的区别&#xff1f;vue2 如何升级到 vue3 ? vue3 组合式API生命周期钩子函数有变化吗&#xff1f; 选项式API 和 组合式API 生命周期…

Java的继承性

1.为什么要有类的继承性&#xff1f;(继承性的好处&#xff09; ① 减少了代码的冗余&#xff0c;提高了代码的复用性② 便于功能的扩展③ 为之后多态性的使用&#xff0c;提供了前提 2.子类继承父类以后有哪些不同&#xff1f; 2.1体现&#xff1a; 一旦子类A继承父类B以…

汇编基础学习

1. 利用ldr向寄存器里面写较大数据&#xff0c;和设置寄存器的某些位 2. 这个lable 不对呢 验证宏值加载到寄存器里是正确的。 pc 的地址是0x80594 当前pc指针加上宏定义值的地址值。 3 打印字符串 字符串加载了8个字节到寄存器里面&#xff0c; 如何调试打印出来呢&#xff1…

数据库设计的原则有哪些

数据库设计是程序开发的核心部分&#xff0c;标准的数据库设计原则和步骤能有效提高开发进度和效率。 数据库设计(Database Design)是指对于一个给定的应用环境&#xff0c;构造最优的数据库模式&#xff0c;建立数据库及其应用系统&#xff0c;使之能够有效地存储数据&#xf…

RK3588平台开发系列讲解(驱动基础篇)中断相关函数

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、获取中断号相关函数二、申请中断函数三、free_irq 函数四、中断处理函数五、中断使能和禁止函数沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 Linux 中断有专门的中断子系统,其实现原理很复杂,但是驱…

Git详解——安装、使用、搭建、IDEA集成

Git 看目录&#xff0c;越往后面越重要 目录一、git是什么&#xff1f;二、为什么要使用Git&#xff1f;三、版本控制工具四、git下载安装以及环境配置五、git基本命令六、git项目搭建七、远程仓库怎么搞&#xff1f;git,gitlab,github,gitee区别八、idea集成Git 一、Git是什…

HBase2.2.2安装(单机、伪分布)

系列文章目录 Ubuntu常见基本问题 Hadoop3.1.3安装&#xff08;单机、伪分布&#xff09; Hadoop集群搭建 HBase2.2.2安装&#xff08;单机、伪分布&#xff09; Zookeeper集群搭建 文章目录 系列文章目录前置条件一、HBase2.2.2安装二、配置环境变量1、问题 三、单机模式1、修…

浅谈数字化

一、数字化转型 数字化转型&#xff08;Digital transformation&#xff09;是建立在数字化转换&#xff08;Digitization&#xff09;、数字化升级&#xff08;Digitalization&#xff09;基础上&#xff0c;进一步触及公司核心业务&#xff0c;以新建一种商业模式为目标的高…

仙人掌之歌——权力的游戏(3)

像疯子一样死去 陈速没想到李通是在香山深处一所疗养院里休养&#xff0c;军方的岗位森严&#xff0c;进去还得把身份证押在门卫室。李通穿着病号服悠哉地晃过来把陈速领了进去。 “通哥&#xff0c;这儿真是个好地方啊。” 陈速由衷地赞叹着&#xff0c;望着大院里古树参天&…

九、Spring Cloud—gateway网关

一、引言 每个微服务都需和前端进行通信&#xff0c;解决每个微服务请求时的鉴权、限流、权限校验、跨域等逻辑&#xff0c;放在一个统一的地方进行使用。 在微服务架构中&#xff0c;网关是一个重要的组件&#xff0c;它作为系统的入口&#xff0c;负责接收所有的客户端请求…

Shiro高级及SaaS-HRM的认证授权

Shiro高级及SaaS-HRM的认证授权 Shiro在SpringBoot工程的应用 Apache Shiro是一个功能强大、灵活的&#xff0c;开源的安全框架。它可以干净利落地处理身份验证、授权、企业会话管理和加密。越来越多的企业使用Shiro作为项目的安全框架&#xff0c;保证项目的平稳运行。 在之…

前端042_图表展现_自适应

自适应 当缩小窗口时,饼图和柱状图不会自动自适应,会被遮挡住。因为 ECharts 本身并不是自适应的,当你父级容器的宽度发生变化的时候需要手动调用它的 .resize() 方法。 其中 vue-element-admin项目中已经实现了自适应效果,只要将对应代码拷贝引用即可。将 vue-element-adm…

Java中的this、package、import

this 在Java中&#xff0c;this的作用和其词义很接近。 它在方法内部使用&#xff0c;即这个方法所属对象的引用&#xff1b; 它在构造器内部使用&#xff0c;表示该构造器正在初始化的对象。 this 可以调用类的属性、方法和构造器 什么时候使用this关键字呢&#xff…

使用kettle进行日志分析

分析日志是一个大数据分析中较为常见的场景。在Unix类操作系统里&#xff0c;Syslog广泛被应用于系统或者应用的日志记录中。Syslog通常被记录在本地文件内&#xff0c;比如Ubuntu内为/var/log/syslog文件名&#xff0c;也可以被发送给远程Syslog服务器。Syslog日志内一般包括产…

机构的专属的线上招生 教学小程序搭建教程

小程序已经成为了很多教育机构的招生、推广重要渠道之一。相比于传统的网站或APP而言&#xff0c;小程序更加轻量级&#xff0c;更加易于传播和分享。在小程序搭建过程中&#xff0c;无需编写复杂的代码&#xff0c;只需要根据模板进行简单的操作&#xff0c;就可以轻松打造自己…

【Web开发技术】JWT令牌技术(信息安全)

文章目录 一、描述二、依赖三、配置四、java文件中的准备五、开始使用 一、描述 说到JWT令牌技术&#xff0c;就需要提到cookie和session两种技术。这两种技术在跨域问题&#xff08;计算机网络的知识&#xff0c;百度可以搜到&#xff0c;就回归重点&#xff09;上存在一定的局…