49 序列化和反序列化

本章重点

理解应用层的作用,初识http协议
理解传输层的作用,深入理解tcp的各项特性和机制
对整个tcp/ip协议有系统的理解
对tcp/ip协议体系下的其他重要协议和技术有一定的了解
学会使用一些网络问题的工具和方法

目录

1.应用层
2.协议概念
3. 网络计算器
4. 序列化和反序列化
5. 协议定制
6. 数据处理
7. 网络函数封装
8. 服务端
9. 客户端
10.结果示例
11. json序列化
12. 添加条件选项
13.再看七层模型

1. 应用层

实际解决问题,满足日常需求的网络程序都在应用层

2. 协议概念

协议是一种“约定”,socket api的接口,在读写数据时,都是按“字符串”的方式来发送接收的,如果我们要传输一些结构化的数据,怎么办?

tcp也称作传输控制协议(什么时候发,发多少,出错了怎么办),传输层是在os内部实现的,是os网络模块部分,将数据交给tcp实际上就是交给os,由于os决定数据的发送,那么收上来的数据就不能完全确定了,有可能是完整的,也有可能是多个报文,或者一部分。所以为了成功的发送和解析报文,应用层就需要协议约定好数据的格式,确定数据的完整性,如果长度不符,就不处理

3. 网络计算器

需要实现一个服务器的计算器,把客户端两个数发过去,然后由服务器计算,最后把结果返回给客户端

约定方案

约定方案一:
客户端发送一个形如“1+1”的字符串
这个字符串有两个操作数,都是整形
两个数字之间会有一个字符是运算符
数字和运算符之间没有空格
。。。
这种情况如果一次性发送了四五组数据,无法区分是一个还是几个报文

约定方案二:
定义结构体表示需要交互的信息
发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则将把字符串串转换回结构体

// proto.h 定义通信的结构体
typedef struct Request {
 int a;
 int b;
} Request;
typedef struct Response {
 int sum;
} Response;
// client.c 客户端核心代码
Request request; 
Response response; 
 
scanf("%d,%d", &request.a, &request.b);
write(fd, request, sizeof(Request));
read(fd, response, sizeof(Response)); 
// server.c 服务端核心代码
Request request;
read(client_fd, &request, sizeof(request));
Response response;
response.sum = request.a + request.b;
write(client_fd, &response, sizeof(response));

直接发送结构体,结构体两边类型一样,这种方法可以,但是不同的设备结构体的对齐方式可能不一样,导致同一个结构体大小不一样。出问题后非常难调试,都是二进制的。如果一出性发好多个,也不好区分一个个报文,不过os内部是这样实现的,各种情况都考虑了

只要保证一端发送,另一端能够正确解析,这种约定就是应用层协议。为了更稳定,可以定下面的约定

4. 序列化和反序列化

上面的约定方式都有不足之处。最终的约定方式以qq消息举例,需要的结构体包含消息内容,昵称,发送时间,将这三个字符串组合为一个字符串发送给客户,客户收到后又重新转换为结构体,解析为发送时间+昵称+内容的结构,双方的内容结构是相同的。也实现了简单的分层,上面负责结构的规划组成序列,下面负责将序列化的数据完整发送和接收,为了方便解析和发送,还需要规定报文之间的分隔。这样序列化和反序列化方便网络收发

在这里插入图片描述

5. 协议定制

协议分两个类,一个是请求类,一个是响应类,请求方用计算类,生成计算式,两个操作数一个操作符,所以成员变量两个int为操作数,一个char操作符。将数据序列化为“x 操作符 y”的结构发送,对方收到后还要提供反序列化为计算类来计算结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结算结果放到结果类,两个成员变量,一个是int的结果,一个是int的返回码,表明结果可不可信。也要提供序列化和反序列化功能,将结果和返回码改变为“结果 返回码”的字符串格式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面的数据可以用来发送了,但如果客户一次性发了很多个数据,或者一个报文也不满足。如何区分每个报文?所以必须为发出的数据添加报头,报头格式定为“长度\n报文\n”,有添加报头也要解析报头
在这里插入图片描述
在这里插入图片描述

protocol.hpp

#pragma once
#include <string>

//分隔符
const std::string black_sep = " ";
const std::string protocol_sep = "\n";

//解决报文外部格式
 //len\n正文\n
    std::string encode(std::string& message)
    {
        std::string package = std::to_string(message.size());
        package += protocol_sep;
        package += message;
        package += protocol_sep;

        return package;
    }

    //len\na + b\n
    bool decode(std::string& message, std::string* content)
    {
        std::size_t pos = message.find(protocol_sep);
        if (pos == std::string::npos)
        {
            return false;
        }

        std::string len_str = message.substr(0, pos);
        std::size_t len = std::stoi(len_str);
        std::size_t total_len = len_str.size() + len + 2;

        //检查长度
        if (message.size() < total_len)
        {
            return false;
        }

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

        return true;
    }

class Request
{
public:
    Request(){}
    Request(int a, int b, char oper)
    {
        _num1 = a;
        _num2 = b;
        _op = oper;
    }
    //a + b
    bool serialize(std::string* out)
    {
        //构建报文有效载荷
        std::string str;
        str += std::to_string(_num1);
        str += black_sep;
        str += _op;
        str += black_sep;
        str += std::to_string(_num2);

        *out = str;
        return true;
    }

    //a + b
    bool deserialize(std::string& in)
    {
        //a
        std::size_t left = in.find(black_sep);
        if (left == std::string::npos)
        {
            return false;         
        }
        std::string part_a = in.substr(0, left);
        // b
        std::size_t right = in.rfind(black_sep);
        if (right == std::string::npos)
        {
            return false;     
        }
        std::string part_b = in.substr(right + 1);
        //+
        if (left + 2 != right)
        {
            return false;
        }

        _op = in[left+1];
        _num1 = std::stoi(part_a);
        _num2 = std::stoi(part_b);

        return true;
    }

    void debugprint()
    {
        cout << "新请求构建完成:" << _num1 << _op << _num2 << endl;
    }

public:
    int _num1;
    int _num2;
    char _op;
};

class Response
{
public:
    Response(){}
    Response(int res, int cod)
    {
        _result = res;
        _code = cod;
    }

    //1000 0
    bool serialize(std::string* out)
    {
        string str = std::to_string(_result);
        str += black_sep;
        str += std::to_string(_code);
        *out = str;

        return true;
    }

    //1000 0
    bool deserialize(std::string& in)
    {
        std::size_t pos = in.find(black_sep);
        if (pos  == std::string::npos)
        {
            return false;
        }

        std::string left = in.substr(0, pos);
        std::string right = in.substr(pos + 1);

        _result = std::stoi(left);
        _code = std::stoi(right);

        return true;
    }

    void debugprint()
    {
        cout << "结果响应完成,result:" << _result << ",code:" << _code << endl;
    }

public:
    int _result;
    int _code;   //0可信,否则表明对应的错误
};


6. 数据处理

有了协议就可以实现数据处理,计算结果并封装的类

枚举各种计算错误的情况,操作符等其他问题用OTHER
在这里插入图片描述

计算函数传入上面的请求类,返回结果响应类。根据操作符进行不同的运算
在这里插入图片描述
数据处理函数将收到的字符串内容转换为请求类,调用计算函数得到结果,并对结果封包返回字符串用来发送
在这里插入图片描述

servercal.hpp

#pragma once
#include "protocol.hpp"

enum
{
    DIVZERO = 1,
    MODZERO,
    OTHER_OPER
};

class ServerCal
{
public:
    ServerCal()
    {

    }

    Response CalculatorHelp(const Request& req)
    {
        Response res(0, 0);
        switch (req._op)
        {
        case '+':
            res._result = req._num1 + req._num2;
            break;
        case '-':
            res._result = req._num1 - req._num2;
            break;
        case '*':
            res._result = req._num1 * req._num2;
            break;
        case '/':
            if (req._num2 == 0)
            {
                res._code = DIVZERO;
            }
            else
            {
                res._result = req._num1 / req._num2;
            }
            break;
        case '%':
            if (req._num2 == 0)
            {
                res._code = MODZERO;
            }
            else
            {
                res._result = req._num1 % req._num2;
                break;
            }
            
        default:
            res._code = OTHER_OPER;
            break;
        }

        return res;
    }

    std::string Calcluator(std::string& package)
    {
        std::string content;
        bool r = decode(package, &content);
        if (!r)
        {
            return "";
        }
        Request req;
        r = req.deserialize(content);
        if (!r)
        {
            return "";
        }
        req.debugprint();
        content = "";
        Response res = CalculatorHelp(req);
        res.debugprint();
        res.serialize(&content);
        content = encode(content); // len\n正文\n

        return content;
    }

    ~ServerCal()
    {

    }
};


7. 网络函数封装

将服务器的socket常用功能封装为scoke类,成员sockfd为socket函数的返回值,提供返回sockfd的函数

Socket.hpp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include "log.hpp"

enum
{
    SOCKERR = 1,
    BINDERR,
    LISERR
};

Log lg;
const int backlog = 5;
class Sock
{
public:
    Sock()
    {

    }

    void Socket()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            lg.logmessage(fatal, "socket error");
            exit(SOCKERR);
        }
    }

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

        int bret = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
        if (bret < 0)
        {
            lg.logmessage(fatal, "bind error");
            exit(BINDERR);
        }
    }

    void Listen()
    {
        int lret = listen(_sockfd, backlog);
        if (lret < 0)
        {
            lg.logmessage(fatal, "listen error");
            exit(LISERR);
        }
    }
    
    int Accept(string* clientip, uint16_t* clientport)
    {
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(_sockfd, (sockaddr*)&peer, &len);
        if (newfd < 0)
        {
            lg.logmessage(warning, "accept error");
            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)
    {
        sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        inet_pton(AF_INET, ip.c_str(), &peer.sin_addr);
        peer.sin_port = htons(port);

        int cret = connect(_sockfd, (const struct sockaddr*)&peer, sizeof(peer));
        if (cret == -1)
        {
            lg.logmessage(warning, "connect error");
            return false;
        }

        return true;
    }

    void Close()
    {
        close(_sockfd);
    }

    int Fd()
    {
        return _sockfd;
    }
    ~Sock()
    {

    }
public:
    int _sockfd;
};

8. 服务端

服务端和通用服务器一样,accept收到连接请求后,创建子进程提供服务,因为数据可能不是一次性接收完的,所以recbuff不断加上读到的内容。同时有可能有多个报文,所以收到数据后进入循环处理,将字符串交给函数模板对象,就是上面的数据处理函数。
在这里插入图片描述

数据处理函数的返回值
在这里插入图片描述

因为可能收到的数据不满足一个报文,所以这个函数里多条判断,报文解析不成功都会返回空,调用得到的字符串内容为空时跳出继续读取。解析成功后返回结果将内容发送给客户端
在这里插入图片描述
解析报文时有多条判断,先找\n,找到说明有数据的长度,然后根据长度获取内容,检查数据长度和计算的长度符不符合。不满足上面条件的不予处理,否则说明数据长度符合,移除解析了的内容

server.hpp

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

using namespace std;
using func_t = std::function<std::string(std::string &package)>;
class server
{
public:
    server(uint16_t port, func_t fun)
    :_port(port), _fun(fun)
    {
    }

    void init()
    {
        //创建套接字
        _listensocket.Socket();
        _listensocket.Bind(_port);
        _listensocket.Listen();

        lg.logmessage(info, "init server done");
    }

    void start()
    {
        signal(SIGCHLD, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        int cnt = 1;
        while (true)
        {
            string ip;
            uint16_t port;

            int sockfd = _listensocket.Accept(&ip, &port);
            if (sockfd < 0)
            {
                continue;
            }

            lg.logmessage(info, "get a new link %d", sockfd);

            if (fork() == 0)
            {
                _listensocket.Close();
                string inbuff_stream;
                // 提供服务
                while (true)
                {
                    char buff[1280];
                    ssize_t n = read(sockfd, buff, sizeof(buff));
                    if (n > 0)
                    {
                        buff[n] = 0;
                        lg.logmessage(debug, "\n%s", buff);
                        inbuff_stream += buff;
                        while (true)
                        {
                            string echo = _fun(inbuff_stream);
                            if (echo.empty())
                            {
                                break;
                            }

                            lg.logmessage(debug, "缓冲区\n%s", inbuff_stream.c_str());
                            lg.logmessage(debug, "结果\n%s", echo.c_str());
                            cout << "次数:" << cnt++ << endl;
                            write(sockfd, echo.c_str(), echo.size());
                        }
                       
                    }
                    else if (n == 0)
                    {
                        break;
                    }
                    else
                    {
                        break;
                    }
                }
                exit(0);
            }

            close(sockfd);
        }
    }

    ~server()
    {

    }
private:
    Sock _listensocket;
    uint16_t _port;
    func_t _fun;
};

server.cc

#include <unistd.h>
#include "server.hpp"
#include "servercal.hpp"
//#include "protocol.hpp"

int main()
{
    ServerCal cal;
    uint16_t port = 8000;
    server *tsvp = new server(port, std::bind(&ServerCal::Calcluator, &cal, std::placeholders::_1));
    tsvp->init();
    daemon(0, 0);
    tsvp->start();
    return 0;
}

std::bind绑定函数和第一个参数

9. 客户端

客户端创建连接,成功生成5次随机的数字和运算符,赋值给请求类,封装后发送,收到内容后用结果类解析
在这里插入图片描述
client.cc

#include <time.h>
#include <unistd.h>
#include <assert.h>
#include "Socket.hpp"
#include "protocol.hpp"

int main()
{
    srand(time(NULL));

    uint16_t port = 8000;
    string ip = "106.54.46.147";
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip.c_str());
    server.sin_port = htons(port);

    const string opers = "+-*/%";

    Sock socket;
    socket.Socket();
    bool r = socket.Connect(ip, port);
    if (!r)
        return 1;

    int cnt = 1;
    while (cnt <= 5)
    {
        cout << "=============第" << cnt << "次测试...." << "============" << endl;
        string package;
        int x = rand() % 100;
        int y = rand() % 100 + 1;
        char op = opers[rand() % opers.size()];

        Request req(x, y, op);
        req.debugprint();

        req.serialize(&package);
        package = encode(package);
        write(socket._sockfd, package.c_str(), package.size());

        char buff[1024];
        int n = read(socket._sockfd, buff, sizeof(buff));

        string inbuff_stream;
        if (n > 0)
        {
            buff[n] = 0;
            inbuff_stream += buff;

            string content;
            bool r = decode(inbuff_stream, &content);
            assert(r);

            Response resp;
            r = resp.deserialize(content);
            assert(r);

            resp.debugprint();
        }

        cout << "=======================================" << endl;
        sleep(1);
        cnt++;
    }
}

日志

#pragma once
#include <stdarg.h>
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <time.h>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

#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 path "log.txt"

class Log
{
public:

    Log(int style = screen)
    {
        printstyle = style;
        dir = "log/";
    }

    void enable(int method)
    {
        printstyle = method;
    }

    const char *leveltostring(int level)
    {
        switch (level)
        {
        case 0:
            return "info";
            break;
        case 1:
            return "debug";
            break;
        case 2:
            return "warning";
            break;
        case 3:
            return "error";
            break;
        case 4:
            return "fatal";
            break;
        default:
            return "none";
            break;
        }
    }

    void printlog(int level, const string &logtxt)
    {
        switch (printstyle)
        {
        case screen:
            cout << logtxt;
            break;
        case onefile:
            printonefile(path, logtxt);
            break;
        case classfile:
            printclassfile(level, logtxt);
            break;
        }
    }

    void logmessage(int level, const char *format, ...)
    {
        time_t t = time(0);
        tm *ctime = localtime(&t);
        char leftbuff[1024];
        sprintf(leftbuff, "[%s]%d-%d-%d %d:%d:%d:", leveltostring(level), ctime->tm_year + 1900,
                ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        char rightbuff[1024];
        va_list s;
        va_start(s, format);
        vsprintf(rightbuff, format, s);
        va_end(s);
        char logtext[2048];
        sprintf(logtext, "%s %s\n", leftbuff, rightbuff);
        //printf(logtext);
        printlog(level, logtext);
    }
 

    void printonefile(const string& logname, const string& logtxt)
    {
        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)
    {
        //log.txt.info
        string filename = dir + path;
        filename += ".";
        filename += leveltostring(level);
        printonefile(filename, logtxt);
    }

    ~Log(){};

private:
    int printstyle;
    string dir; //分类日志,放入目录中
};

// int sum(int n, ...)
// {
//     int sum = 0;
//     va_list s;
//     va_start(s, n);

//     while (n)
//     {
//         sum = sum + va_arg(s, int);
//         n--;
//     }

//     return sum;
// }

10. 结果示例

在这里插入图片描述
可以加入守护进程,在初始化服务器后开启守护
在这里插入图片描述

11. json序列化

上面的序列化和反序列化功能都是字符串处理,比较麻烦。有一种数据交换格式json,有序列化和反序列化的功能,可以用json代替。用条件编译试试json发送数据

如果没有先安装

sudo yum install -y jsoncpp-devel

序列化
先创建json里的通用变量,Json::Value,以键值对的方式赋值,key和value
在这里插入图片描述
再用一个通用变量嵌套一个value类
在这里插入图片描述
调用序列化功能生成字符串打印,有两种风格,style内容换行易读在这里插入图片描述
反序列化
创建value变量,创建read对象,调用parse功能解析内容
在这里插入图片描述
利用key-value格式提取内容,嵌套类型定义value变量获取,再提取一次
在这里插入图片描述
输出结果
在这里插入图片描述

修改protocol文件
用#ifdef #else #endif的格式条件编译,json方式写在else的情况里

Request

		Json::Value root;
        root["x"] = _num1;
        root["y"] = _num2;
        root["op"] = _op;
        Json::FastWriter w;
        *out = w.write(root);

        return true;
		Json::Value root;
        Json::Reader r;
        r.parse(in, root);
        _num1 = root["x"].asInt();
        _num2 = root["y"].asInt();
        _op = root["op"].asInt();

        return true;

Response

		Json::Value root;
        root["res"] = _result;
        root["code"] = _code;
        Json::FastWriter w;
        *out = w.write(root);

        return true;
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);
        _result = root["res"].asInt();
        _code = root["code"].asInt();

        return true;

示例
在这里插入图片描述
序列化的工具还有protobuf,二进制,更注重效率,可读性不如json,适用于内部使用

12. 添加条件选项

在这里插入图片描述
$号可以引入定义的常量,#会注释后面的内容

13. 再看七层模型

在这里插入图片描述
会话层的维护是通过server创建子进程提供服务的,接到链接就创建一个会话
表示层就是上面定义的协议,就是结构体字符串等固有的数据格式,网络标准格式就是序列化添加报头这些动作后的数据
应用层就是处理数据的计算器功能
所以表示层和会话层应用层很难在os实现,根据不同的场景有不同的格式和功能,内容也会有文字声音图像等都有可能,无法在os全部实现

如果客户端连上一直不发数据,会占用资源,可以对时间进行判断,超时直接挂掉

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

网络爬虫原理及其应用

你是否想知道Google 和 Bing 等搜索引擎如何收集搜索结果中显示的所有数据。这是因为搜索引擎对其档案中的所有页面建立索引&#xff0c;以便它们可以根据查询返回最相关的结果。网络爬虫使搜索引擎能够处理这个过程。 本文重点介绍了网络爬虫的重要方面、网络爬虫为何重要、其…

Docker学习(3):镜像使用

当运行容器时&#xff0c;使用的镜像如果在本地中不存在&#xff0c;docker 就会自动从 docker 镜像仓库中下载&#xff0c;默认是从 Docker Hub 公共镜像源下载。 一、列出镜像列表 可以使用 docker images 来列出本地主机上的镜像。 各个选项说明&#xff1a; REPOSITORY&am…

vue源码2

vue之mustache库的机理其实是将模板字符串转化为tokens 然后再将 tokens 转化为 dom字符串&#xff0c;如下图 对于一般的将模板字符串转化为dom字符串&#xff0c;这样不能实现复杂的功能 let data {name:小王,age:18 } let templateStr <h1>我叫{{name}},我今年{{ag…

MySQl创建数据库与管理表

创建数据库与管理表 基础知识 完整的数据存储过程 同时&#xff0c;数据库系统层次 数据库服务器 -》 数据库 -》 数据表 -》 行与列 数据库命名规则&#xff1a; 库名、表名不得超过30字符&#xff1b;变量名&#xff08;字段&#xff09;不超过29字符 只能包含A-Z、a-z、…

hive3从入门到精通(二)

第15章:Hive SQL Join连接操作 15-1.Hive Join语法规则 join分类 在Hive中&#xff0c;当下版本3.1.2总共支持6种join语法。分别是&#xff1a; inner join&#xff08;内连接&#xff09;left join&#xff08;左连接&#xff09;right join&#xff08;右连接&#xff09;…

AIGC001-latent-diffusion(SD)第一次让文生图如此生动有趣!

AIGC001-latent-diffusion(SD)第一次让文生图如此生动有趣&#xff01; 文章目录 0 论文工作1 论文方法2 效果 0 论文工作 通过将图像形成过程分解为去噪自编码器的连续应用&#xff0c;扩散模型&#xff08;DMs&#xff09;实现了对图像数据等方面的最先进的综合结果。这些方…

MySQL---通用语法及分类

目录 一、SQL通用语法 二、 SQL分类 1.DDL 1.1 DDL数据库操作 1.2 DDL表操作---查询 1.3 DDL表操作---创建​编辑 1.4 DDL表操作---数据类型 1.5 DDL表操作---修改 1.6 DDL表操作---删除 1.7 DDL总结 2. 图形化界面工具DataGrip 2.1 创建 2.2 使用 3. DML 3.1 DML介绍 3.2 DM…

C语言.数据结构.顺序表

1.顺序表的概念及结构 1.1线性表 线性表&#xff08;linear list&#xff09;是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff0c;常见的线性表&#xff1a;顺序表、链表、栈、队列、字符串… 线性表在逻辑上是线性结构&#xff0c;…

Golang net/http标准库常用方法(三)

大家好&#xff0c;针对Go语言 net/http 标准库&#xff0c;将梳理的相关知识点分享给大家~~ 围绕 net/http 标准库相关知识点还有许多章节&#xff0c;请大家多多关注。 文章中代码案例只有关键片段&#xff0c;完整代码请查看github仓库&#xff1a;https://github.com/hltfa…

面试八股之JVM篇3.6——垃圾回收——强引用、弱引用、虚引用、软引用

&#x1f308;hello&#xff0c;你好鸭&#xff0c;我是Ethan&#xff0c;一名不断学习的码农&#xff0c;很高兴你能来阅读。 ✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。 &#x1f3c3;人生之义&#xff0c;在于追求&#xff0c;不在成败&#xff0c;勤通…

LVS精益价值管理系统 LVS.Web.ashx SQL注入漏洞复现

0x01 产品简介 LVS精益价值管理系统是杭州吉拉科技有限公司研发的一款专注于企业精益化管理和价值流优化的解决方案。该系统通过集成先进的数据分析工具、可视化的价值流映射技术和灵活的流程改善机制,帮助企业实现高效、低耗、高质量的生产和服务。 0x02 漏洞概述 LVS精益…

全国数据库管理系统设计赛-人大金仓内核实训安排正式发布

作为数据库领域国家队&#xff0c;人大金仓积极响应国家战略&#xff0c;通过赛题设计、内核技术支撑及赛前培训等多方面&#xff0c;大力支持全国大学生计算机系统能力大赛-数据库管理系统设计大赛成功举办。目前第二届全国大赛正在火热报名中&#xff0c;各种奖项等你来拿&am…

RabbitMQ 发布订阅

RabbitMQ 发布订阅视频学习地址&#xff1a; 简单模式下RabbitMQ 发布者发布消息 消费者消费消息 Publist/Subscribe 发布订阅 在 RabbitMQ 中&#xff0c;发布订阅模式是一种消息传递方式&#xff0c;其中发送者&#xff08;发布者&#xff09;不会将消息直接发送到特 定的…

Linux文本处理三剑客(详解)

一、文本三剑客是什么&#xff1f; 1. 对于接触过Linux操作系统的人来说&#xff0c;应该都听过说Linux中的文本三剑客吧&#xff0c;即awk、grep、sed&#xff0c;也是必须要掌握的Linux命令之一&#xff0c;三者都是用来处理文本的&#xff0c;但侧重点各不相同&#xff0c;a…

Docker-镜像迁移的三种方式=>备份恢复公有仓库私有仓库

制作好的镜像要被别人使用&#xff0c;有三种方式&#xff1a; 1.先备份镜像&#xff0c;别人通过u盘或者其它方式拷贝后&#xff0c;再恢复镜像&#xff0c;这种方式比较麻烦 2.将制作的镜像上传到公共镜像仓库&#xff0c;被别人拉取后使用&#xff0c;但可能存在网络不通畅或…

嵩山为什么称为三水之源

三水指黄河、淮河、济河&#xff0c;这三条河流环绕在嵩山周边。 黄河横亘在嵩山北部&#xff0c;其支流伊洛河从西南方环绕嵩山&#xff0c;然后汇入黄河。济河&#xff0c;古称济水&#xff0c;源自济源王屋山&#xff0c;自身河道在东晋时代被黄河夺占&#xff0c;从此消失。…

【Spring MVC】_SpringMVC项目返回数据

目录 1. 注解使用示例 1.1 使用Controller注解 1.2 使用RestController注解 1.3 使用Controller与ResponseBody注解 2. 关于ResponseBody注解 前文已经介绍过使用Controller注解向前端返回一个HTML页面&#xff0c;接下来将介绍向前端返回数据。 关于Controller和RestCon…

算法金 | Dask,一个超强的 python 库

本文来源公众号“算法金”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Dask&#xff0c;一个超强的 python 库 1 Dask 概览 在数据科学和大数据处理的领域&#xff0c;高效处理海量数据一直是一项挑战。 为了应对这一挑战&am…

初学者都能掌握的操作符(中)

&#xff08;1&#xff09;位操作符&#xff08;& | ^&#xff09; &&#xff1a;&#xff08;按二进制位“与”&#xff09; 也就是两个数的每一位二进制数按照 “与” 的算法&#xff0c;如下&#xff1a; int a 3 ,b 5 ; c a & b; 我们首先写出a和b的二进…

Java面试八股之Synchronized和ReentrantLock的区别

Synchronized和ReentrantLock的区别 实现级别&#xff1a; synchronized是Java的一个关键字&#xff0c;属于JVM层面的原生支持&#xff0c;它通过监视器锁&#xff08;Monitor&#xff09;来实现同步控制&#xff0c;无需手动获取和释放锁。 ReentrantLock是java.util.conc…