协议的定制之序列化与反序列化 | 守护进程

news2025/1/17 15:27:25

目录

一、再谈协议

二、序列化与反序列化

三、网络计算器的简单实现

四、网络计算器完整代码

五、代码改进

六、守护进程

七、Json序列化与反序列化

八、netstat


一、再谈协议

是对数据格式和计算机之间交换数据时必须遵守的规则的正式描述。简单的说了,网络中的计算机要能够互相顺利的通信,就必须讲同样的语言,语言就相当于协议。

为了使数据在网络上能够从源主机到达目的主机,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议。只有使用相同的协议,主机间才能进行通信。 

二、序列化与反序列化

今天,我们作为客户端,想要让服务端帮助我们进行计算,然后将结果返回给我们,这就是一个网络计算器了。如果我们要计算两数相加,比如 a+b,那么现在问题来了,我们应该如何将a,+,b这三个数据传输给服务器呢?是一个一个传呢,还是整体传输呢?

如果客户端将这些结构化的数据单独一个个的发送到网络当中,那么服务端从网络当中获取这些数据时也只能一个个获取,此时服务端还需要考虑,哪个是左操作数,哪个是操作符,哪个是右操作数。所以这样不合适。

对于一起发送。a,+,b三个数据组成一组结构化的数据,我们首先想到使用一个结构体将三个数据打包,一起发送给服务器,而我们所用的各种通信函数只允许我们传输字符串,所以我们必须要将结构化的数据先转化成字符串,然后才能发送给服务器。而服务器接收到字符串后,则必须将字符串进行解析,转成结构化的数据,进行计算,然后将结果转成字符串发回客户端。这个过程就叫做“序列化”和“反序列化”。

~ 序列化:就是将对象的状态信息转换为可以存储或传输的形式(字节序列)的过程。

~ 反序列化:就是把字节序列恢复为对象的过程。

具体怎么实现,我们通过编写网络计算器来进行讲解。

三、网络计算器的简单实现

对于网络计算器的客户端,我们使用一个结构体去存储左右操作数和操作符,这个结构体我们称为请求(Request),在向服务端发送请求的时候,我们需要转成字符串才能发送,于是我们需要有序列化的函数,当然,收到结果后,我们也需要反序列化函数,将字符串结果转成结构化结果。

对于服务端,我们使用一个结构体去存储计算结果和标识计算结果的正确性,因为客户端可能会发过来除0或者模0的请求。在收到客户端的请求后,我们需要反序列化函数将结果转成结构化数据,方便计算,发回结果的时候,需要序列化函数将结果转成字符串才能发送。

四、网络计算器完整代码

Sock.hpp

我们对网络套接字进行一个封装。

#pragma once
#include <iostream>
#include <string>
#include <cstdbool>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class Sock
{
public:
    const static int gmv = 20;
    Sock()
    {
    }

    int Socket()
    {
        // 1.创建套接字
        int sock = socket(AF_INET, SOCK_STREAM, 0);
        if (sock < 0)
        {
            std::cout << "创建套接字失败!" << std::endl;
            exit(1);
        }
        std::cout << "创建套接字成功!" << std::endl;
        return sock;
    }

    void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
    {
        // 2.进行绑定
        struct sockaddr_in src_server;
        bzero(&src_server, sizeof(src_server));
        src_server.sin_family = AF_INET;
        src_server.sin_port = htons(port);
        inet_pton(AF_INET, ip.c_str(), &src_server.sin_addr);
        socklen_t len = sizeof(src_server);
        if (bind(sock, (struct sockaddr *)&src_server, len) < 0)
        {
            std::cout << "绑定失败!" << std::endl;
            exit(2);
        }
        std::cout << "绑定成功!" << std::endl;
    }

    void Listen(int sock)
    {
        // 3.开始监听,等待连接
        if (listen(sock, gmv) < 0)
        {
            std::cout << "监听失败!" << std::endl;
            exit(3);
        }

        std::cout << "服务器监听成功!" << std::endl;
    }

    int Accept(int sock, std::string *ip, uint16_t *port)
    {
        // 4.获取链接
        struct sockaddr_in client_sock;
        socklen_t len = sizeof(client_sock);
        int serversock = accept(sock, (struct sockaddr *)&client_sock, &len);
        if (serversock < 0)
        {
            std::cout << "获取链接失败!" << std::endl;
            return -1;
        }
        if (port)
            *port = ntohs(client_sock.sin_port);
        if (ip)
            *ip = inet_ntoa(client_sock.sin_addr);
        return serversock;
    }

    bool Connect(int sock, const std::string &ip, const uint16_t &port)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(ip.c_str());
        server.sin_port = htons(port);
        if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == 0)
            return true;
        else
            return false;
    }

    ~Sock()
    {
    }
};

TcpServer.hpp: 

#pragma once
#include "Sock.hpp"
#include <functional>
#include <pthread.h>

using func_t = std::function<void(int)>;

class TcpServer;
class threaddata
{
public:
    threaddata(int sock, TcpServer *server) : sock_(sock), server_(server) {}
    ~threaddata() {}

public:
    int sock_;
    TcpServer *server_;
};

class TcpServer
{
private:
    static void *threadRoutine(void *arg)
    {
        pthread_detach(pthread_self());
        threaddata *td = (threaddata *)arg;
        td->server_->excute(td->sock_);
        close(td->sock_);
        delete td;
    }

public:
    TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
    {
        listensock_ = sock_.Socket();
        sock_.Bind(listensock_, port, ip);
        sock_.Listen(listensock_);
    }

    void BindServer(func_t func)
    {
        func_ = func;
    }

    void excute(int sock)
    {
        func_(sock);
    }

    void Start()
    {
        for (;;)
        {
            std::string clientip;
            uint16_t clientport;
            int sock = sock_.Accept(listensock_, &clientip, &clientport);
            if (sock == -1)
                continue;

            threaddata *td = new threaddata(sock, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, td);
        }
    }

    ~TcpServer()
    {
        if (listensock_ >= 0)
            close(listensock_);
    }

private:
    int listensock_;
    Sock sock_;
    func_t func_;
};

Protocol.hpp:协议定制

我们规定序列化的结果是"x_ op_ y_”,即:左操作数,空格,操作符,空格,左操作数。

#pragma once
#include <iostream>
#include <string>
#include <cstdbool>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>

#define SPACE " "
#define SPACELEN strlen(SPACE)

std::string Recv(int sock)
{
    char buffer[1024];
    ssize_t s = recv(sock, buffer, sizeof buffer, 0);
    if (s == 0)
        std::cout << "客户端退出!" << std::endl;
    else if (s > 0)
        return buffer;
    else
        std::cerr << "客户端退出!" << std::endl;

    return "";
}

void Send(int sock, const std::string &str)
{
    ssize_t s = send(sock, str.c_str(), str.size(), 0);
}

class Request
{
public:
    std::string Serialize()
    {
        std::string requeststr = std::to_string(x_);
        requeststr += SPACE;
        requeststr += op_;
        requeststr += SPACE;
        requeststr += std::to_string(y_);
        return requeststr;
    }

    // x_ op_ y_
    bool Deserialization(const std::string &str)
    {
        std::size_t left = str.find(SPACE);
        if (left == std::string::npos)
            return false;
        std::size_t right = str.rfind(SPACE);
        if (right == std::string::npos)
            return false;

        x_ = atoi(str.substr(0, left).c_str());
        y_ = atoi(str.substr(right + SPACELEN).c_str());
        op_ = str[left + SPACELEN];
        return true;
    }

public:
    Request() {}
    Request(const int &x, const int &y, const char &op) : x_(x), y_(y), op_(op) {}
    ~Request() {}

public:
    int x_;
    int y_;
    char op_;
};

class Response
{
public:
    // code_ result_
    std::string Serialize()
    {
        std::string str = std::to_string(code_);
        str += SPACE;
        str += std::to_string(result_);
        return str;
    }

    bool Deserialization(std::string &str)
    {
        std::size_t pos = str.find(SPACE);
        if (pos == std::string::npos)
            return false;
        code_ = atoi(str.substr(0, pos).c_str());
        result_ = atoi(str.substr(pos + SPACELEN).c_str());
        return true;
    }

public:
    Response(const int &result = 0, const int &code = 0) : result_(result), code_(code) {}
    ~Response() {}

public:
    int result_;
    int code_;
};

CalServer.cc:

#include "tcpserver.hpp"
#include "Protocol.hpp"
#include <signal.h>
#include <memory>

static void usage(std::string proc)
{
    std::cout << proc << " port" << std::endl;
}

Response CalculatorHelper(const Request &req)
{
    Response res(0, 0);
    switch (req.op_)
    {
    case '+':
        res.result_ = req.x_ + req.y_;
        break;
    case '-':
        res.result_ = req.x_ - req.y_;
        break;
    case '*':
        res.result_ = req.x_ * req.y_;
        break;
    case '/':
        if (0 == req.y_)
            res.code_ = 1;
        else
            res.result_ = req.x_ / req.y_;
        break;
    case '%':
        if (0 == req.y_)
            res.code_ = 2;
        else
            res.result_ = req.x_ % req.y_;
        break;
    default:
        res.code_ = 3;
        break;
    }
    return res;
}

void Calculator(int sock)
{
    while (true)
    {
        std::string buffer = Recv(sock);
        if (!buffer.empty())
        {
            Request req;
            req.Deserialization(buffer);
            Response res = CalculatorHelper(req);
            std::string sendstr = res.Serialize();
            Send(sock, sendstr);
        }
        else break;
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    signal(SIGPIPE, SIG_IGN);
    uint16_t server_port = atoi(argv[1]);
    std::unique_ptr<TcpServer> sev(new TcpServer(server_port));
    sev->BindServer(Calculator);
    sev->Start();

    return 0;
}

CalClient.cc:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <unistd.h>
#include "Sock.hpp"
#include "Protocol.hpp"

static void usage(std::string proc)
{
    std::cout << proc << " ip port" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);
    Sock sock;
    int sockfd = sock.Socket();
    if (!sock.Connect(sockfd, ip, port))
    {
        std::cout << "连接出错!" << std::endl;
        exit(2);
    }
    std::cout << "连接成功!" << std::endl;

    while (true)
    {
        Request req;
        std::cout << "请输入x # ";
        std::cin >> req.x_;
        std::cout << "请输入y # ";
        std::cin >> req.y_;
        std::cout << "请输入op # ";
        std::cin >> req.op_;

        std::string sendstr = req.Serialize();
        Send(sockfd, sendstr);

        std::string recstr = Recv(sockfd);
        Response res;
        res.Deserialization(recstr);
        std::cout << "code_: " << res.code_ << std::endl;
        std::cout << "result_: " << res.result_ << std::endl;
    }

    return 0;
}

运行结果:

五、代码改进

其实,上面我们 Protocol.hpp 中的代码是有问题的?为什么呢?

我们早就说过,TCP是面向字节流的,所以在从网络中读取的时候,并不能保证发送和读取到的是一个完整的 x_ op_ y_ 结构,可能只发送或者读取了 x_ op_,也可能是 x_ op_ y_  x_ op_ y_  x_ op_ y_。所以客户端和服务器不能准确区分,因此我们需要对发送和读取进行控制,使得每次发送和读取到的都是一个完整的报文。

我们所使用的TCP协议是传输层协议。在TCP层,拥有两个缓冲区:发送缓冲区和接收缓冲区。我们调用的所有发送函数,并不是直接把数据发送到网络中,而是将数据由应用层拷贝到TCP的发送缓冲区中,由TCP协议决定如何发送这些数据和每次发送多少数据。接收函数也不是直接从网络中获取数据,而是从发送缓冲区拷贝数据到应用层。至于数据如何到TCP的接收缓冲区,也是完全由TCP协议决定。

因此,发送函数和接收函数本质上是拷贝函数。

因此我们可以将序列化的数据定成这种结构  length\r\nx_ op_ y_\r\n(或者 length\r\ncode_ result_\r\n) ,我们通过length来标定正文长度,使用\r\n来分隔length和正文。

知道length,就可以知道怎样读取多长的数据了,这样就可以读取到完整的报文。而发送的时候任然是面向字节流式的发送,不过我们需要添加 length 和特殊符号 \r\n,再发送。

在Protocol.hpp:协议定制里面,我们还需要修改下面的代码:Encode:将序列化的字符串转成  length\r\nx_ op_ y_\r\n。Decode:将 length\r\nx_ op_ y_\r\n 转成 x_ op_ y_(即拿到正文)。

#pragma once
#include <iostream>
#include <string>
#include <cstdbool>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>

#define SPACE " "
#define SPACELEN strlen(SPACE)
#define SEP "\r\n"
#define SEPLEN strlen(SEP)

bool Recv(int sock, std::string *out)
{
    char buffer[1024];
    ssize_t s = recv(sock, buffer, sizeof buffer - 1, 0);
    if (s == 0)
    {
        std::cout << "客户端退出!" << std::endl;
        return false;
    }
    else if (s > 0)
    {
        buffer[s] = 0;
        *out += buffer;
    }
    else
    {
        std::cerr << "客户端退出!" << std::endl;
        return false;
    }

    return true;
}

void Send(int sock, const std::string &str)
{
    ssize_t s = send(sock, str.c_str(), str.size(), 0);
}

// len\r\nx op y\r\n
std::string Decode(std::string buffer)
{
    std::size_t pos = buffer.find(SEP);
    if (pos == std::string::npos)
        return "";
    int size = atoi(buffer.substr(0, pos).c_str());  // 完整正文的长度
    int mainsize = buffer.size() - pos - 2 * SEPLEN; // 正文长度
    if (mainsize >= size)
    {
        buffer.erase(0, pos + SEPLEN);
        std::string result = buffer.substr(0, size);
        buffer.erase(0, size + SEPLEN);
        return result;
    }
    else
        return "";
}

std::string Encode(std::string &s)
{
    std::string ret = std::to_string(s.size());
    ret += SEP;
    ret += s;
    ret += SEP;
    return ret;
}

class Request
{
public:
    std::string Serialize()
    {
        std::string requeststr = std::to_string(x_);
        requeststr += SPACE;
        requeststr += op_;
        requeststr += SPACE;
        requeststr += std::to_string(y_);
        return requeststr;
    }

    // x_ op_ y_
    bool Deserialization(const std::string &str)
    {
        std::size_t left = str.find(SPACE);
        if (left == std::string::npos)
            return false;
        std::size_t right = str.rfind(SPACE);
        if (right == std::string::npos)
            return false;

        x_ = atoi(str.substr(0, left).c_str());
        y_ = atoi(str.substr(right + SPACELEN).c_str());
        op_ = str[left + SPACELEN];
        return true;
    }

public:
    Request() {}
    Request(const int &x, const int &y, const char &op) : x_(x), y_(y), op_(op) {}
    ~Request() {}

public:
    int x_;
    int y_;
    char op_;
};

class Response
{
public:
    // code_ result_
    std::string Serialize()
    {
        std::string str = std::to_string(code_);
        str += SPACE;
        str += std::to_string(result_);
        return str;
    }

    bool Deserialization(std::string &str)
    {
        std::size_t pos = str.find(SPACE);
        if (pos == std::string::npos)
            return false;
        code_ = atoi(str.substr(0, pos).c_str());
        result_ = atoi(str.substr(pos + SPACELEN).c_str());
        return true;
    }

public:
    Response(const int &result = 0, const int &code = 0) : result_(result), code_(code) {}
    ~Response() {}

public:
    int result_;
    int code_;
};

CalServer.cc:

#include "tcpserver.hpp"
#include "Protocol.hpp"
#include <signal.h>
#include <memory>

static void usage(std::string proc)
{
    std::cout << proc << " port" << std::endl;
}

Response CalculatorHelper(const Request &req)
{
    Response res(0, 0);
    switch (req.op_)
    {
    case '+':
        res.result_ = req.x_ + req.y_;
        break;
    case '-':
        res.result_ = req.x_ - req.y_;
        break;
    case '*':
        res.result_ = req.x_ * req.y_;
        break;
    case '/':
        if (0 == req.y_)
            res.code_ = 1;
        else
            res.result_ = req.x_ / req.y_;
        break;
    case '%':
        if (0 == req.y_)
            res.code_ = 2;
        else
            res.result_ = req.x_ % req.y_;
        break;
    default:
        res.code_ = 3;
        break;
    }
    return res;
}

void Calculator(int sock)
{
    std::string str;
    while (true)
    {
        bool rest = Recv(sock, &str);
        if (!rest)
            break;
        std::string package = Decode(str);
        if (!package.empty())
        {
            Request req;
            req.Deserialization(package);
            Response res = CalculatorHelper(req);
            std::string sendstr = res.Serialize();
            sendstr = Encode(sendstr);
            Send(sock, sendstr);
        }
        else
            continue;
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }
    signal(SIGPIPE, SIG_IGN);
    uint16_t server_port = atoi(argv[1]);
    std::unique_ptr<TcpServer> sev(new TcpServer(server_port));
    sev->BindServer(Calculator);
    sev->Start();

    return 0;
}

 CalClient.cc:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <unistd.h>
#include "Sock.hpp"
#include "Protocol.hpp"

static void usage(std::string proc)
{
    std::cout << proc << " ip port" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);
    Sock sock;
    int sockfd = sock.Socket();
    if (!sock.Connect(sockfd, ip, port))
    {
        std::cout << "连接出错!" << std::endl;
        exit(2);
    }
    std::cout << "连接成功!" << std::endl;

    bool quit = false;
    std::string buffer;

    while (!quit)
    {
        Request req;
        std::cout << "请输入x # ";
        std::cin >> req.x_;
        std::cout << "请输入y # ";
        std::cin >> req.y_;
        std::cout << "请输入op # ";
        std::cin >> req.op_;

        std::string sendstr = req.Serialize();
        sendstr = Encode(sendstr);
        Send(sockfd, sendstr);

        while (true)
        {
            bool ret = Recv(sockfd, &buffer);
            if (!ret)
            {
                quit = true;
                break;
            }
            std::string recstr = Decode(buffer);
            if (recstr.empty())
                continue;
            Response res;
            res.Deserialization(recstr);
            std::cout << "code_: " << res.code_ << std::endl;
            std::cout << "result_: " << res.result_ << std::endl;
            break;
        }
    }
    close(sockfd);

    return 0;
}

六、守护进程

后台进程:就是在后台运行,不占用用户终端的进程。

前台进程:前台进程是与用户直接交互的进程(和终端关联的进程)。可以直接获取键盘的输入。

如下:我们的bash就是一个最常见的前台进程,我们输入各种指令,他就能返回相应的结果。

而我们上面所写的服务器进程在启动后,也是在前台运行的,也是一个前台进程。如下:

从上图我们也看到:服务器进程在启动后,如果我们再输入各种指令的话,将不会有任何结果。因为xshell登录后,只允许有一个前台进程和多个后台进程。 

再如下:我们使用管道创建多个进程,

除了PID,PPID之外,PGID我们称为组ID,SID我们称为会话ID。

这三个被同时创建的进程组成了一个进程组,他们的PGID都是23440,也是第一个进程的PID。所以一个进程组的PGID是第一个进程的PID。

我们在登陆了xshell后,xshell会给用户提供一种会话机制,在会话中,包含了给用户提供服务的bash进程,终端,以及用户自己在该会话中启动的进程。当xshell退出登录,会话也会退出。

如下图:

而现在,我希望会话退出后,服务器进程任然也可以运行。那么我们就需要让服务器进程自成一个会话。这样服务器进程就成为了一个守护进程。守护进程也是一种后台进程。

我们使用 setsid()函数,就可以让某个进程变成自成会话。注:setsid要成功被调用,必须保证当前进程不是进程组的组长。

所以,我们自己写一个方法来让服务器进程变成一个守护进程。

一般步骤:

1、忽略信号:SIGPIPE,SIGCHLD

2、不要让自己是进程组的组长,fork()

3、调用setsid()函数

4、因为守护进程不能向显示器打印消息,所以我们需要将标准输出、标准错误和标准输入进行重定向。这里我们需要使用Linux中的一个文件:/dev/null。其特点就是,任何向其中写入的内容都会被其丢弃。因为文件中没有内容,所以读取时什么也读取不到。

daemon.hpp:

#pragma once
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void mydaemon()
{
    // 1.忽略信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    // 2.不要让自己成为组长
    if (fork() > 0)
        exit(0);

    // 3.调用setsid()
    setsid();

    int devnull = open("/dev/null", O_RDONLY | O_WRONLY);
    if (devnull > 0)
    {
        dup2(0, devnull);
        dup2(1, devnull);
        dup2(2, devnull);
        close(devnull);
    }
}

然后,我们只需要在服务器运行前调用这个函数即可。当然,服务器代码中任何向显示器打印的函数都不应该调用了。

CalServer.cc的main函数:

通过查询,发现 MyServer的父进程PPID为1,也就是操作系统。所以说,守护进程也是一种孤儿进程。 

七、Json序列化与反序列化

对于序列化和反序列化,上面我们自己的方案其实也很多问题,所以我们除了可以自定义方案以外,还可以使用别人的已经实现好了的方案。比如我们接下来讲到的:json。

使用前,我们需要安装json库: sudo yum install jsoncpp-devel

Protocol.hpp:

#include <jsoncpp/json/json.h>

class Request
{
public:
    std::string Serialize()
    {
        Json::Value root;
        root["x"] = x_;
        root["y"] = y_;
        root["op"] = op_;
        Json::FastWriter writer;
        return writer.write(root);
    }

    // x_ op_ y_
    bool Deserialization(const std::string &str)
    {
        Json::Value root;
        Json::Reader reader;
        reader.parse(str, root);
        x_ = root["x"].asInt();
        y_ = root["y"].asInt();
        op_ = root["op"].asInt();
        return true;
    }

public:
    Request() {}
    Request(const int &x, const int &y, const char &op) : x_(x), y_(y), op_(op) {}
    ~Request() {}

public:
    int x_;
    int y_;
    char op_;
};

class Response
{
public:
    // code_ result_
    std::string Serialize()
    {
        Json::Value root;
        root["code"] = code_;
        root["result"] = result_;
        Json::FastWriter writer;
        return writer.write(root);
    }

    bool Deserialization(std::string &str)
    {
        Json::Value root;
        Json::Reader reader;
        reader.parse(str, root);
        code_ = root["code"].asInt();
        result_ = root["result"].asInt();
        return true;
    }

public:
    Response(const int &result = 0, const int &code = 0) : result_(result), code_(code) {}
    ~Response() {}

public:
    int result_;
    int code_;
};

八、netstat

netstat:我们可以通过netstat命令来查看当前网络的状态,这里我们可以选择携带nlup选项。

netstat常用选项:

-n:直接使用IP地址,而不通过域名服务器。
-l:显示监控中的服务器的Socket。
-t:显示TCP传输协议的连线状况。
-u:显示UDP传输协议的连线状况。
-p:显示正在使用Socket的程序识别码和程序名称。

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

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

相关文章

SpringCloud 之 服务消费者

前提 便于理解请我修改了本地域名》这里!!! 127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com 127.0.0.1 eureka7003.comRest学习实例之消费者 创建一个消费者去消费 消费者模块展示 1、导入依赖 <!-- 实体类api自己创建的模块 Web 部分依赖展示--><de…

jmeter之跨线程关联

1&#xff09;_setproperty函数&#xff1a;将值保存成jmeter属性 2&#xff09;_property函数&#xff1a;在其他线程组中使用property函数读取属性 一、跨线程接口引用变量 1. 法一&#xff1a;jmeter自带函数_setProperty和_property 1. 1线程组 01 创建登录的【HTTP请求】…

OriginPro作图之箱线图

前言 箱线图(Box-plot) 又称为盒须图、盒式图或箱线图&#xff0c;是一种用作显示一组数据分散情况资料的统计图。因型状如箱子而得名。 本文将结合实例阐述其意义和绘图过程。 箱线图简介 箱线图(Boxplot) 也称箱须图( Box-whisker Plot)&#xff0c;是利用数据中的五个统计量…

FANUC机器人socket通讯硬件配置

一、添加机器人选配包 Fanuc机器人要进行socket通讯&#xff0c;需要有机器人通讯的选配包&#xff0c;1A05B-2600-R648 User Socket Msg&#xff0c;1A05B-2600-R632 KAREL&#xff0c;1A05B-2600-R566 KAREL Diagnostic&#xff0c;1A05B-2600-J971 KAREL Use Sprt FCTN。 二…

有没有一本从电路开始讲然后汇编再到C语言的书?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「C语言的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 没有一本书或者几本书能把这整…

【NPU】A800-9000服务器8*Ascend 910 B的HCCS测试

HCCS集合通信带宽数据 HCCS集合通信带宽数据timeline信息在msprof_*.json文件的HCCS层级展示 summary信息在hccs_*.csv文件汇总。 支持的型号 Atlas 训练系列产品 Atlas A2训练系列产品 测试命令 npu-smi info -t topo结果展示 NPU0 NPU1 NPU2 NPU3 …

微信小程序实现美食检索功能

1、打开浏览器搜索&#xff1a;腾讯位置服务 2、注册一个账号&#xff0c;有账号的直接登陆就行 3、注册登陆成功后&#xff0c;点击控制台 4、进入控制台后点击我的应用——>创建应用 5、添加key,注意看注释 6、key添加成功后&#xff0c;开始分配额度&#xff08;配额&…

人工智能(AI)与地理信息技术(GIS)的融合:开启智能地理信息时代

随着科技的不断发展&#xff0c;人工智能&#xff08;AI&#xff09;和地理信息技术&#xff08;GIS&#xff09;的应用越来越广泛&#xff0c;两者的结合更是为许多行业带来了前所未有的变革。本文将以“人工智能&#xff08;AI&#xff09;地理信息技术&#xff08;GIS&#…

【C++】5.C语言/C++内存管理

一、C/C内存分布 栈中存储的是局部变量&#xff0c;函数参数&#xff0c;返回值等 堆主要用于动态内存分配 数据段用以存储全局数据和静态数据 代码段存储可执行代码和常量 二、C语言和C中的内存管理方式 在C语言中&#xff0c;我们使用 malloc、calloc、realloc、free来进…

理解与使用Linux设备树编译器(DTC)

这里写目录标题 设备树简介设备树编译器&#xff08;DTC&#xff09;安装DTC使用DTC实例&#xff1a;编辑设备树小结参考资料 Linux设备树编译器&#xff08;DTC&#xff09;是一个关键工具&#xff0c;用于处理嵌入式Linux系统中的设备树文件。本文将介绍设备树的概念、DTC的基…

深入理解 Srping IOC

什么是 Spring IOC&#xff1f; IOC 全称&#xff1a;Inversion of Control&#xff0c;翻译为中文就是控制反转&#xff0c;IOC 是一种设计思想&#xff0c;IOC 容器是 Spring 框架的核心&#xff0c;它通过控制和管理对象之间的依赖关系来实现依赖注入&#xff08;Dependenc…

每日一题:托普利茨矩阵

给你一个 m x n 的矩阵 matrix 。如果这个矩阵是托普利茨矩阵&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果矩阵上每一条由左上到右下的对角线上的元素都相同&#xff0c;那么这个矩阵是 托普利茨矩阵 。 示例 1&#xff1a; 输入&#xff1a;matrix…

最新windows版本erlang26.0和rabbitmq3.13下载

Erlang下载 官网下载&#xff1a;https://www.erlang.org/patches/otp-26.0 百度网盘&#xff1a;https://pan.baidu.com/s/1xU4syn14Bh7QR-skjm_hOg 提取码&#xff1a;az1t RabbtitMQ下载 官网下载&#xff1a;https://www.rabbitmq.com/docs/install-windows 百度网盘…

【C++题解】1302. 是否适合晨练?

问题&#xff1a;1302. 是否适合晨练&#xff1f; 类型&#xff1a;分支 题目描述&#xff1a; 夏天到了&#xff0c;气温太高&#xff0c;小明的爷爷每天有晨练的习惯&#xff0c;但有时候温度不适合晨练&#xff1b;小明想编写一个程序&#xff0c;帮助爷爷判断温度是否适合…

同一工程中不同RS的问题结论

目录 MeshDevice/deviceAll && /wvp/device ​编辑 故意改成mesh下的RS,结果包裹了&#xff1a; sys2/redishealth ​编辑 ​编辑 原因解析 MeshDevice/deviceAll && /wvp/device 测试结果&#xff1a;都使用 import com.gbcom.wvp.domain.vo.RS; 返…

C语言编程题_3D接雨水

接雨水的题目描述如下。 (1) 2D接雨水&#xff1a; 字节员工是不是个个都会接雨水 &#xff1b; (2) 3D接雨水&#xff1a; 407. 接雨水 II &#xff1b; (3) 3D接雨水&#xff1a; 字节人都会的 3D接雨水 。 问题描述 难度&#xff1a;困难 给你一个 m x n 的矩阵&#xff…

【历史版本火狐浏览器下载】

历史版本火狐浏览器下载 介绍 火狐浏览器是一款开源的互联网浏览器&#xff0c;由Mozilla基金会开发。它的历史版本可以追溯到2002年&#xff0c;以下是一些重要的历史版本介绍&#xff1a; Firefox 1.0&#xff08;2004年&#xff09;- 这是火狐浏览器的第一个正式版本&…

安装多个MySQL版本时如何连接到不同的数据库

当安装多个版本的数据库时&#xff0c;不同版本的端口名不一样&#xff0c;可以使用以下命令进行连接 mysql -uroot -p数据库密码 -h主机名 -P端口号 数据库主机名默认是localhost&#xff0c;端口号默认是3306&#xff0c;当安装多个版本数据库时&#xff0c;需要记住数据库的…

vim+xxd 编辑16进制

1. vim -b mib 2. 在vim 中执行 %!xxd, 这样就可以输入16进制&#xff1a; 3. 输入完成后&#xff0c;在vim中 执行 %!xxd -r 切换至原模式&#xff1b; 4. 保存退出即可 5. 重新打开mib文件&#xff1a;vim -b mib 6. 在vim 中执行 %!xxd, 查看是否符合预期&#xff1a;…

java 语法中的数据类型

Java有哪些数据类型&#xff1f; Java语言数据类型分为两种&#xff1a;基本数据类型和引用数据类型。 Java基本数据类型范围和默认值&#xff1a; 需要注意一下&#xff0c;对于布尔类型&#xff1a; 如果 boolean 是单独使用&#xff08;在编译之后使用 Java 虚拟机中的 int…