【网络】应用层协议(自定义协议)(序列和反序列化)

news2024/11/23 11:26:33

应用层协议(自定义协议)(序列和反序列化)

  • 一、引言--应用层的使用
  • 二、应用层
    • 1、网络版本计算器
      • (1)协议定制和序列反序列化
      • (2)网络版计算器协议定制
        • i、封装有效载荷(默认上面图是Request的,下面图是Response的)
        • ii、封装报头(单独出来的函数)
        • iii、分离有效载荷(默认上面图是Request的,下面图是Response的)
        • iv、解析报文(单独出来的函数)
        • v、测试结果
      • (3)计算器函数
      • (4)计算器与网络连接(网络服务代码)
      • (5)附加:Socket封装
      • (6)服务端代码
      • (7)测试结果
        • 测试结果Debug
        • 解决方法:每一次都删掉规定好的报文
      • (8)客户端编写
      • (9)客户端多个请求同时发送
    • 2、json(用来自动支持序列反序列化)
    • 3、用json进行序列反序列化结果
  • 三、重谈OSI7层模型


一、引言–应用层的使用

在这里插入图片描述

二、应用层

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

1、网络版本计算器

(1)协议定制和序列反序列化

在这里插入图片描述

结构化数据,所有主机都要是一样的结构体类型(协议的定制),再通过网络的序列和反序列化方便数据的收发。

(2)网络版计算器协议定制

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

i、封装有效载荷(默认上面图是Request的,下面图是Response的)

其实就是将这个结构体转化成为"x op y"!和"result code"!
在这里插入图片描述
在这里插入图片描述

ii、封装报头(单独出来的函数)

“len”\n"s"

在这里插入图片描述

iii、分离有效载荷(默认上面图是Request的,下面图是Response的)

将string s 进行分离出来,分离成整型或者char类型的。
在这里插入图片描述
在这里插入图片描述

iv、解析报文(单独出来的函数)

在这里插入图片描述

v、测试结果

在这里插入图片描述

(3)计算器函数

CalHelper函数是进行完加减乘除取模后返回结果的函数,Calculator是将计算结果进行封装完报头的函数。

    Response CalHelper(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 = DIVERRO;
            }
            else
            {
                resp.result = req.x / req.y;
            }
            break;
        }
        case '%':
        {
            if (req.y == 0)
            {
                resp.code = MOERROR;
            }
            else
            {
                resp.result = req.x % req.y;
            }
            break;
        }
        default:
            resp.code = OTHERERROR;
            break;
        }
        return resp;
    }
    // "len"\n"10 + 20"\n
    std::string Calculator(std::string &package)
    {
        std::string content;
        bool r = Decode(package, &content); // 分离出有效载荷 // "len"\n"10 + 20"\n
        if (!r)
        {
            return "";
        }
        // 反序列化 -- 分离开每一个元素
        Request req;
        r = req.Deserialize(content); // x=10 op=+ y=20
        if (!r)
        {
            return "";
        }
        // 处理结果
        content = "";
        Response resp = CalHelper(req); // result=30 code=0
        // 序列化
        resp.Serialize(&content); // "30 0"
        // 封装报头
        content = Encode(content); // "len"\n"30 0"\n
        return content;
    }

(4)计算器与网络连接(网络服务代码)

代码逻辑为:InitTcpServer函数是用来进行监听套接字的设置绑定和监听的,RunTcpServer函数是用来将底层的监听套接字拿到上层来使用并进行提供服务的。

#pragma once

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

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 InitTcpServer()
    {
        _listensocketfd.Socket();
        _listensocketfd.Bind(_port);
        _listensocketfd.Listen();
        lg(Info, "init servercal...");
        return true;
    }
    void RunTcpServer()
    {
        signal(SIGCHLD, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        while (true)
        {
            std::string clientip;
            uint16_t clientport;
            int socketfd = _listensocketfd.Accept(&clientip, &clientport);
            if (socketfd < 0)
            {
                continue;
            }
            lg(Info, "accept a new link..., sockfd:%d, clientip:%s, clientport:%d", socketfd, clientip.c_str(), clientport);
            // 提供服务
            // pid_t id = fork();
            if (fork() == 0)
            {
                _listensocketfd.Close();
                std::string inbuffer_stream;
                // 数据计算
                while (true)
                {
                    char buffer[128];
                    ssize_t n = read(socketfd, buffer, sizeof(buffer));
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        inbuffer_stream += buffer;

                        lg(Debug, "debug:%s", inbuffer_stream.c_str());
                        // while (true)
                        //{
                        std::string info = _callback(inbuffer_stream);
                        if (info.empty())
                            continue;
                        // break;
                        // lg(Debug, "debug, response:\n%s", info.c_str());
                        // lg(Debug, "debug:\n%s", inbuffer_stream.c_str());
                        write(socketfd, info.c_str(), info.size());
                        //}
                    }
                    else if (n == 0)
                    {
                        break;
                    }
                    else
                        break;
                }

                exit(0);
            }
            close(socketfd);
        }
    }
    ~TcpServer()
    {
    }

private:
    Sock _listensocketfd;
    uint16_t _port;
    func_t _callback;
};

回调函数示意:
在这里插入图片描述

(5)附加:Socket封装

#pragma once

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

//extern Log lg;

enum
{
    SOCKETERR = 2,
    BINDERR,
    LISTENERR
};

const int backlog = 10;

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

public:
    void Socket()
    {
        _socketfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_socketfd < 0)
        {
            lg(Fatal, "socket create err, %d:%s", errno, strerror(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(_socketfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, %d:%s", errno, strerror(errno));
            exit(BINDERR);
        }
    }
    void Listen()
    {
        if (listen(_socketfd, backlog) < 0)
        {
            lg(Fatal, "listen error, %d:%s", errno, strerror(errno));
            exit(LISTENERR);
        }
    }
    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(_socketfd, (struct sockaddr *)&peer, &len);
        if (newfd < 0)
        {
            lg(Warning, "accept error, %d:%s", errno, strerror(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;
    }
    int Connect()
    {
        return 0;
    }
    void Close()
    {
        close(_socketfd);
    }

private:
    int _socketfd;
};

(6)服务端代码

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Servercal.hpp"

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

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t clientport = std::stoi(argv[1]);
    ServerCal cal;
    TcpServer *tsvp = new TcpServer(clientport, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));
    tsvp->InitTcpServer();
    tsvp->RunTcpServer();
    return 0;
}

(7)测试结果

在这里插入图片描述

测试结果Debug

在这里插入图片描述

解决方法:每一次都删掉规定好的报文

在这里插入图片描述

(8)客户端编写

Socket.hpp:
在这里插入图片描述
ClientCal.cc:

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

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

// ./clientcal 127.0.0.1 8888
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    uint16_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];
    Sock socketfd;
    socketfd.Socket();
    bool r = socketfd.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);
        std::cout << "这是新的发出去的请求:\n" << package;
        write(socketfd.Fd(), package.c_str(), package.size());

        char buffer[128];
        ssize_t n = read(socketfd.Fd(), buffer, sizeof(buffer)); // 保证不了读到的是一个完整的报文
        if (n > 0)
        {
            buffer[n] = 0;
            inbuffer_stream += buffer;
            std::string content;
            bool rqs = Decode(inbuffer_stream, &content);
            assert(rqs);

            Response resp;
            rqs = resp.Deserialize(content);
            assert(rqs);

            resp.DebugPrint();
        }
        std::cout << "=================================================" << std::endl;
        sleep(1);
        cnt++;
    }
    socketfd.Close();
    return 0;
}

测试结果:
在这里插入图片描述

(9)客户端多个请求同时发送

在这里插入图片描述

在这里插入图片描述

2、json(用来自动支持序列反序列化)

安装命令:sudo yum install -y jsoncpp-devel
在这里插入图片描述
安装成功:
在这里插入图片描述

头文件:

#include <jsoncpp/json/json.h>

使用:
FastWriter:
测试用例一(序列化):

int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    Json::FastWriter w;
    std::string res = w.write(root);

    std::cout << res << std::endl;
    return 0;
}

编译:
g++ test.cc -ljsoncpp
在这里插入图片描述
测试用例二(反序列化):
FastWriter:

int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    Json::FastWriter w;
    std::string res = w.write(root);
    
    sleep(3);

    Json::Value v;
    Json::Reader r;
    r.parse(res, v);

    int x = v["x"].asInt();
    int y = v["y"].asInt();
    char op = v["op"].asInt();
    std::string desc = v["desc"].asString();
    Json::Value temp = v["test"];
    std::cout << x << std::endl;
    std::cout << y << std::endl;
    std::cout << op << std::endl;
    std::cout << desc << std::endl;
    return 0;
}

在这里插入图片描述

测试用例三(序列化):
StyleWriter:

int main()
{
    Json::Value part1;
    part1["haha"] = "haha";
    part1["hehe"] = "hehe";


    Json::Value root;
    root["x"] = 100;
    root["y"] = 200;
    root["op"] = '+';
    root["desc"] = "this is a + oper";
    root["test"] = part1;

    Json::StyleWriter w;
    std::string res = w.write(root);

    std::cout << res << std::endl;
    return 0;
}

在这里插入图片描述

3、用json进行序列反序列化结果

Makefile:

.PHONY:all
all:servercal clientcal

Flag=-DMySelf=1
Lib=-ljsoncpp

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

.PHONY:clean
clean:
	rm -f servercal clientcal

Protocol.hpp:

#pragma once
// 定制协议
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>

// #define MySelf 1

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

std::string Encode(std::string &content)
{
    // 封装报头
    std::string package = std::to_string(content.size());
    package += protocol_sep;
    package += content;
    package += protocol_sep;
    return package;
}

bool Decode(std::string &package, std::string *content) // "len"\n"x op y"\n
{
    // package = len_str + content_str + 2 总长度等于长度字符串长度+正文长度+两个\n的长度
    size_t pos = package.find(protocol_sep);
    if (pos == std::string::npos)
    {
        return false;
    }
    std::string len_str = package.substr(0, pos);
    // 分理出长度
    size_t len = std::stoi(len_str);
    size_t total_len = len_str.size() + len + 2;
    if (package.size() < total_len)
    {
        return false;
    }
    // 完整报文--提取有效载荷
    *content = package.substr(pos + 1, len);
    // 移除报文,防止多余报文影响
    package.erase(0, total_len);
    return true;
}

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" => "len"\n"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;
#else
        Json::Value root;
        root["x"] = x;
        root["y"] = y;
        root["op"] = op;
        Json::FastWriter w;
        *out = w.write(root);
#endif
        return true;
    }
    bool Deserialize(const std::string &in) // "x op y"
    {
#ifdef MySelf
        size_t leftpos = in.find(blank_space_sep);
        if (leftpos == std::string::npos)
        {
            return false;
        }
        std::string part_x = in.substr(0, leftpos);

        size_t rightpos = in.rfind(blank_space_sep);
        if (rightpos == std::string::npos)
        {
            return false;
        }
        std::string part_y = in.substr(rightpos + 1);
        if (leftpos + 1 != rightpos - 1)
        {
            return false;
        }
        op = in[leftpos + 1];
        x = std::stoi(part_x);
        y = std::stoi(part_y);
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

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

public:
    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
        // 构建有效载荷
        // "len"\n"result code"
        std::string s = std::to_string(result);
        s += blank_space_sep;
        s += std::to_string(code);

        *out = s;
#else
        Json::Value root;
        root["result"] = result;
        root["code"] = code;
        Json::FastWriter w;
        *out = w.write(root);
#endif
        return true;
    }
    bool Deserialize(const std::string &in) // "result code"
    {
#ifdef MySelf
        size_t pos = in.find(blank_space_sep);
        if (pos == std::string::npos)
        {
            return false;
        }
        std::string res = in.substr(0, pos);
        std::string cod = in.substr(pos + 1);
        result = std::stoi(res);
        code = std::stoi(cod);
#else
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);

        result = root["result"].asInt();
        code = root["code"].asInt();
#endif
        return true;
    }
    void DebugPrint()
    {
        std::cout << "结果响应完成, 结果是:" << "result:" << result << "  " << "code:" << code << std::endl;
    }

public:
    int result;
    int code; // 0表示可信,非零表示不可信,相对应的数字表示相对应的错误原因
};

在这里插入图片描述

三、重谈OSI7层模型

在这里插入图片描述
这三层不在内核中实现,是我们手动实现。

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

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

相关文章

数据结构——单链表OJ题(上)

目录 一、移除链表元素 1.思路 2.注意 3.解题 二、反转链表 思路1&#xff1a;三指针翻转法 &#xff08;1&#xff09;注意 &#xff08;2&#xff09;解题 思路2&#xff1a;头插法 &#xff08;1&#xff09;注意 &#xff08;2&#xff09;解题 三、链表的中间结…

depcheck 前端依赖检查

介绍 depcheck 是一款用于检测项目中 未使用依赖项 的工具。 depcheck 通过扫描项目文件&#xff0c;帮助你找出未被引用的依赖&#xff0c;从而优化项目。 优势&#xff1a; 简单易用: 仅需几个简单的命令&#xff0c;就能够扫描并列出未使用的依赖项&#xff0c;让你快速了…

The Schematic workflow failed. See above.

在使用 ng new 新建Angular项目的时候会报一个错误&#xff1a;The Schematic workflow failed. See above. 解决办法&#xff1a; 只需要在后面加上 --skip-install 参数&#xff0c;就不会报错了。 ng new myapp --skip-install

打工人电脑里都需要的远程控制软件有哪些?这4款不能错过

不巧前几天台风&#xff0c;实在没办法到公司&#xff0c;但是项目还得继续&#xff0c;这时候远程控制电脑的技巧可谓是帮了我大忙了。不知道平常的你偶尔会不会也需要远程控制电脑的操作&#xff0c;如果有就继续看下去吧。 1.向日魁远程控制 直通车>>https://down.o…

AJAX-Promise 详解

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 前言 一、Promise基本概念 1.1 定义 1.2 状态 1.3 构造函数 二、Promise基本用法 2.1 then() 2.2 ca…

ThinkPHP一对一关联模型的运用(ORM)

一、序言 最近在写ThinkPHP关联模型的时候一些用法总忘&#xff0c;我就想通过写博客的方式复习和整理下一些用法。 具体版本&#xff1a; topthink/framework&#xff1a;6.1.4topthink/think-orm&#xff1a;2.0.61 二、实例应用 1、一对一关联 1.1、我先设计了两张表&#x…

根据题意写出完整的css,html和js代码【购物车模块页面及功能实现】

🏆本文收录于《CSDN问答解惑-专业版》专栏,主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!! 问题描述 根据题意写出完…

基于微信小程序+SpringBoot+Vue的社区超市管理系统(带1w+文档)

基于微信小程序SpringBootVue的社区超市管理系统(带1w文档) 基于微信小程序SpringBootVue的社区超市管理系统(带1w文档) 为了让商品信息的管理模式进行升级&#xff0c;也为了更好的维护商品信息&#xff0c;社区超市管理系统的开发运用就显得很有必要&#xff0c;因为它不仅可…

C# 植物大战僵尸

Winform 版本开发 高效率、流畅植物大战僵尸 git地址&#xff1a;冯腾飞/植物大战僵尸

go语言day19 使用git上传包文件到github Gin框架入门

git分布式版本控制系统_git切换head指针-CSDN博客 获取请求参数并和struct结构体绑定_哔哩哔哩_bilibili &#xff08;gin框架&#xff09; GO: 引入GIn框架_go 引入 gin-CSDN博客 使用git上传包文件 1&#xff09;创建一个github账户&#xff0c;进入Repositories个人仓…

我在百科荣创企业实践——简易函数信号发生器(6)

对于高职教师来说,必不可少的一个任务就是参加企业实践。这个暑假,本人也没闲着,报名参加了上海市电子信息类教师企业实践。7月8日到13日,有幸来到美丽的泉城济南,远离了上海的酷暑,走进了百科荣创科技发展有限公司。在这短短的一周时间里,我结合自己的教学经验和企业的…

buu做题(8)

[安洵杯 2019]easy_web 查看源代码可以发现一长串的base64编码 就是页面上的一张图片 回到原页面,url上面也有一些奇怪的参数 经过两次base64和一次hex 解密后得到 555.png 应该就是包含着页面上的这张图片 然后尝试将index.php 按照这样的方式编码, 看看能不能包含到 TmprMl…

后端解决跨域(Cross-Origin Resource Sharing)(三种方式)

注解CrossOrigin 控制层的类上或者方法上加注解CrossOrigin 实现接口并重写方法 Configuration public class CorsConfig implements WebMvcConfigurer {Overridepublic void addCorsMappings(CorsRegistry registry) {// 设置允许跨域的路径registry.addMapping("/**&qu…

算法通关:006_4二分查找:寻找数组中的峰值

文章目录 描述主要代码全部代码运行结果总结 二分法不一定只能用在有序数组中。 描述 leetcode&#xff1a;162 主要代码 //二分法查找峰值public static int findPeakElement(int[] arr){if (arr.length 1){//randomArray()不会出现arr null的情况return 0;}//先检查 0…

LabVIEW操作系列1

系列文章目录 我的记录&#xff1a; LabVIEW操作系列 文章目录 系列文章目录前言五、特殊用法5.1 取值范围表示5.2 对输入值取值范围进行限定5.3 控制多个While循环停止运行。5.4 获取按钮上的文本5.5 获取按钮上的文本【进阶】 六、使用步骤1.引入库2.读入数据 七、其余功能7.…

二叉树以及堆的实现

树 树的定义及概念 树是⼀种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09; 个有限结点组成⼀个具有层次关系的集合。把它叫做树是因为它看起来像⼀棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的。 有⼀个特殊的结点&#xff0c;称…

[Meachines] [Easy] Admirer Adminer远程Mysql反向+Python三方库函数劫持权限提升

信息收集 IP AddressOpening Ports10.10.10.187TCP:21,22,80 $ nmap -p- 10.10.10.187 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 21/tcp open ftp vsftpd 3.0.3 22/tcp open ssh OpenSSH 7.4p1 Debian 10deb9u7 (protocol 2.0) | ssh-hostkey: | …

Redis:十大数据类型

键&#xff08;key&#xff09; 常用命令 1. 字符串&#xff08;String&#xff09; 1.1 基本命令 set key value 如下&#xff1a;设置kv键值对&#xff0c;存货时长为30秒 get key mset key value [key value ...]mget key [key ...] 同时设置或者获取多个键值对 getrange…

万物互联,触手可及“2024南京智慧城市,物联网,大数据展会”

在金秋送爽的11月&#xff0c;南京这座历史悠久而又充满活力的城市&#xff0c;即将迎来一场科技盛宴——2024南京智慧城市、物联网、大数据展会。这不仅是一场技术的集会&#xff0c;更是未来生活蓝图的预览&#xff0c;它汇聚了全球顶尖的科技企业、创新者及行业精英&#xf…

制作PE启动U盘 预防电脑无法正常开机进入系统

PE 是一种简化版的便携式操作系统&#xff0c;它可以直接装载在 U 盘里运行。通过它我们能做非常多的应急操作&#xff0c;比如删除文件、卸载软件、拷贝数据、格式化硬盘、重装系统等。 在电脑无法正常开机进入系统&#xff0c;身边又没有其他电脑时&#xff0c;手头有个 PE …