[计算机网络]认识“协议”

news2025/1/18 12:02:07

认识“协议”

文章目录

  • 认识“协议”
    • 序列化和反序列化
    • 网络计算器
      • 引入Sock类
      • 设计协议
      • 编写服务端类
      • 启动服务端
      • 编写客户端类
      • 启动客户端
      • 程序测试

序列化和反序列化

在网络体系结构中,应用层的应用程序会产生数据,这个数据往往不是简单的一段字符串数据,而是具有一定意义的结构化数据,应用层要想在网络中发送这个结构化数据,就要将其转化成报文结构,而这个将应用程序产生的结构化数据转化成报文的过程就是序列化

数据被序列化成报文后,应用程序进程就可以使用操作系统提供了网络接口,将报文交付给操作系统,操作系统就会完成后续的操作,将数据封装,然后让网卡硬件发送出去。

报文经过网络被接收方主机的网卡硬件接收后,接收方主机的操作系统就会获取网卡硬件中接收到的报文并进行分用,然后在进程调用操作系统提供的网络接口时,将报文交给进程,进程接收到报文后,将报文转化成对应的结构化数据的过程就是反序列化。

image-20231102153656332

程序产生的结构化数据要被序列化,程序接收报文要将其反序列化,不论是序列化和反序列化都要遵守一定规则,这个规则就是双方的“协议”,此外协议还包括如何给有效载荷添加报头成为一个完整报文,如何给完整报文去除报头获得有效载荷、结构化数据的结构。一个结构化的数据,按照一定的规则序列化封装后,再根据序列化的规则进行封装操作的逆操作,也就是反序列化,一定能得到原始的结构化数据,这就是"协议"的作用。

网络计算器

为了更好的体会序列化和反序列化的过程,我们进行编码,实现一个网络计算器。为了实现这个网络计算器,我们需要实现一个进行计算的服务端, 实现一个发送请求的客户端。我们使用客户端把要计算的两个操作数发过去, 然后由服务端进行计算, 最后再把结果返回给客户端。

引入Sock类

要实现的是网络计算器,因此一定是需要使用套接字的,因此我们封装一个Sock类,类内部编写一些套接字操作函数。使用这个类方便后续代码的编写,具体代码实现如下:

static const int backlog = 64;
static const int defaultfd = -1;

class Sock
{
    public:
    Sock() : _sock(defaultfd)
    {
    }

    void Socket() // 创建套接字
    {
        _sock = socket(AF_INET, SOCK_STREAM, 0); // TCP
        if (_sock < 0)
        {
            LogMessage(Fatal, "create socket error:%s", strerror(errno)); // 打印信息到日志文件中
            exit(SOCKET_ERROR);
        }
    }

    void Bind(uint16_t port) // 绑定IP地址和端口号
    {
        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);
        if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            LogMessage(Fatal, "bind socket error:%s", strerror(errno));
            exit(BIND_ERROR);
        }
    }

    void Listen()
    {
        if (listen(_sock, backlog) < 0)
        {
            LogMessage(Fatal, "listen socket error:%s", strerror(errno));
            exit(LISTEN_ERROR);
        }
    }

    int Accept(std::string *clientip, uint16_t *clientport)
    {
        struct sockaddr_in temp;
        memset(&temp, 0, sizeof(temp));
        socklen_t len = sizeof(temp);
        int sock = accept(_sock, (struct sockaddr *)&temp, &len);
        if (sock < 0)
        {
            LogMessage(Warning, "accept socket error:%s", strerror(errno));
        }
        else
        {
            *clientip = inet_ntoa(temp.sin_addr);
            *clientport = htons(temp.sin_port);
        }
        return sock;
    }

    int Connect(std::string& serverip, uint16_t& serverport)
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(serverip.c_str());
        server.sin_port = htons(serverport);
        return connect(_sock, (struct sockaddr*)&server, sizeof(server));
    }

    int Fd()
    {
        return _sock;
    }

    ~Sock()
    {
        if (_sock != defaultfd)
        {
            close(_sock);
        }
    }

    private:
    int _sock;
};

说明: 想了解套接字编写的更多细节可以查看博主的另一篇博客网络套接字编程(二)-CSDN博客,该Sock类内部使用了一个LogMessage函数,该函数的作用是打印日志信息到日志文件中,想了解该日志组件的更多细节可以查看博主的另一篇博客网络套接字编程(三)-CSDN博客。

设计协议

设计协议,指定客户端和服务端序列化和反序列化的具体规则,具体代码如下:

#define SEP " "
#define SEP_LEN strlen(SEP)
#define HEAD_SEP "\r\n"
#define HEAD_SEP_LEN strlen(HEAD_SEP)

std::string addHeader(const std::string &send_string)//给有效载荷添加报头
{
    std::string s;
    s += std::to_string(send_string.size());
    s += HEAD_SEP;
    s += send_string;
    s += HEAD_SEP;
    return s;
}

std::string removeHeader(int len, const std::string &package)//去除报头还原有效载荷
{    
    std::string temp = package.substr(package.size() - HEAD_SEP_LEN - len, len);  
    return temp;
}

int readPackage(int sock, std::string &inbuffer, std::string *package)//读取报文函数
{   
    char buffer[1024];
    ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0);//读取数据
    if (s <= 0)
        return -1;//读取数据出错
    inbuffer += buffer;
    int pos = inbuffer.find(HEAD_SEP, 0);
    if (pos == std::string::npos) return 0;//读取到的数据中没有完整的报文
    std::string lenStr = inbuffer.substr(0, pos);//获取有效载荷长度字符串
    int len = toInt(lenStr);//计算有效载荷长度
    int targetPackageLen = lenStr.size() + len + 2 * HEAD_SEP_LEN;//计算一个完整报文长度 
    if (targetPackageLen > inbuffer.size()) return 0;//读取到的数据中没有完整的报文
    *package = inbuffer.substr(0, targetPackageLen);//获取报文
    inbuffer.erase(0, targetPackageLen);//清除已读取的报文,为下一次读取做准备

    return len;
}

int stringSplit(const std::string &inStr, const std::string& sep, std::vector<std::string> *result)//对有效载荷字符切割
{
    size_t start = 0;

    while(start< inStr.size())
    {
        int pos = inStr.find(sep, start);
        if(pos == std::string::npos)//没找到对应字符串
            break;
        result->push_back(inStr.substr(start, pos - start));
        start = pos + sep.size();
    }
    if (start < inStr.size())//将最后一个操作数写入
        result->push_back(inStr.substr(start));

    return result->size();
}

int toInt(const std::string &str)//将字符串转化成整形数据
{
    return atoi(str.c_str());
}

class Request // 请求结构体
{
    public:
    Request()
    {
    }
    Request(int x, int y, char op) : _x(x), _y(y), _op(op)
    {
    }
    bool Serialize(std::string *outStr) // 序列化
    {
        *outStr = ""; // 清空

        //将结构化数据转化成有效载荷字符串
        std::string x_string = std::to_string(_x);
        std::string y_string = std::to_string(_y);
        *outStr = x_string + SEP + _op + SEP + y_string;

        return true;
    }
    bool Deserialize(const std::string &inStr) // 反序列化
    {
        // 将有效载荷字符串反序列化
        std::vector<std::string> result;
        int ret = stringSplit(inStr, SEP, &result);
        if (ret != 3)//操作符和操作数的数量不正确
            return false;
        if (result[1].size() != 1)//操作符错误
            return false;
        _x = toInt(result[0].c_str());
        _y = toInt(result[2].c_str());
        _op = result[1][0];
        return true;
    }
    void Print()//打印数据
    {
        std::cout << "req.x:" << _x << " req.y:" << _y << " req.op" << _op << std::endl; 
    }
    public:
    int _x;   // 左操作数
    int _y;   // 右操作数
    char _op; // 操作符
};

class Response // 相应结构体
{
    public:
    Response()
    {
    }
    Response(int result, int exitcode):_result(result), _exitcode(exitcode)
    {
    }
    bool Serialize(std::string *outStr) // 序列化
    {
        *outStr = ""; // 清空
        //将结构化数据转化成有效载荷字符串
        std::string ret_string = std::to_string(_result);
        std::string excd_string = std::to_string(_exitcode);
        *outStr = ret_string + SEP + excd_string;

        return true;
    }
    bool Deserialize(const std::string &inStr) // 反序列化
    {
        std::vector<std::string> result;
        int ret = stringSplit(inStr, SEP, &result);
        if (ret != 2)
            return false;

        _result = toInt(result[0].c_str());
        _exitcode = toInt(result[1].c_str());
        return true;

    }
    void Print()//打印数据
    {
        std::cout << "resp.result:" << _result << " req.exitcode:" << _exitcode << std::endl; 
    }
    public:
    int _result;   // 计算结果
    int _exitcode; // 错误码
};

报文的设计

为了让客户端和服务端读取数据后,将每个有效载荷分离开来,在发送数据前,要为其添加应用层报头,报文的结构:有效载荷长度\r\n有效载荷\r\n,在客户端和服务端读取数据时,只有读到一个完整的报文结构,才认为读到了一个有效载荷,才会将该报文结构读取。

因此添加报头的方式如下: 有效载荷 ->有效载荷长度\r\n有效载荷\r\n,对应上述代码中的addHeader函数,参数是有效载荷字符串,返回值是添加报头后报文结构字符串。

去除报头的方式如下: 有效载荷长度\r\n有效载荷\r\n ->有效载荷对应上述代码中的removeHeader函数,参数是报文结构字符串,返回值是去除报头后的有效载荷字符串。

由于实现的是计算器功能,因此我们要处理的数据是类似于1+1的字符串,将其转换成有效载荷后为1 + 1,中间用空格隔开。

读取报文的方式

由于要同读取一个完整报文结构的方式来将每个有效载荷分离开,因此实现了readPackage函数,用于将每个报文结构分割。

该函数有三个参数,第一个参数是读取数据的文件描述符,第二个参数是应用层级别的缓冲区,第三个参数是获取一个完整报文结构输出型参数,从文件中读取数据后,先将数据加载到应用层级缓冲区,然后使用该缓冲区内部的数据进行报文结构的查找,如果当前缓冲区内没有一个完整的报文结构,就退出该函数,等待下一次读取文件。

报文结构查找的方式如下:首先报文的结构是有效载荷长度\r\n有效载荷\r\n,因此先寻找\r\n结构,通过该结构找到有效载荷长度,然后通过有效载荷长度计算该完整报文结构的长度,然后判断缓冲区内部数据的大小是否超过该报文结构长度。最后将这个报文结构写入输出性参数中。

引入Request类和Response类

Request类是客户端向服务端发送请求所使用的结构化数据,类内部实现了将Request类序列化成只包含有效载荷的字符串的Serialize成员函数,还实现了将只包含有效载荷的字符串反序列化成Request类的Deserialize函数。

Response类服务端向客户端发送相应所使用的结构化数据,类内部实现了将Response类序列化成只包含有效载荷的字符串的Serialize成员函数,还实现了将只包含有效载荷的字符串反序列化成Response类的Deserialize函数。

Request类和Response类的序列化和反序列化都使用了stringSplit函数和toInt函数,其中stringSplit函数是有效载荷切割函数,toInt函数是将字符串转化成整形函数。stringSplit函数的实现方式如下:由于有效载荷中操作数和操作符是由空格隔开的,因此使用空格将左操作数、操作符、右操作数分离开来。落实到代码中就是设计了一个存储字符串的vector结构的result变量,查找第一个空格将空格前的数据写入result[0],查找第二个空格将两个空格之间的数据写入result[1],将第二个空格后的数据写入result[2],由于是形如1 + 1的字符串,因此result[0]为左操作数,result[1]为操作数符,result[2]为右操作数

编写服务端类

我们将计算器服务端封装成类,类内部实现服务端的初始化,服务端启动的函数,计算器任务执行函数,具体代码实现如下:

using func_t = std::function<Response(const Request &)>;

class TcpServer;

class ThreadData
{
    public:
    ThreadData(int sock, std::string clientip, uint16_t clientport, TcpServer *tsvr)
        : _sock(sock), _clientip(clientip), _clientport(clientport), _tsvr(tsvr)
        {
        }

    public:
    int _sock;
    std::string _clientip;
    uint16_t _clientport;
    TcpServer *_tsvr;
};

class TcpServer
{
    public:
    TcpServer(func_t func, uint16_t port) : _func(func), _port(port)
    {
    }
    void initServer()
    {
        _listensock.Socket();
        _listensock.Bind(_port);
        _listensock.Listen();
        LogMessage(Info, "Init server done,listensock:%d", _listensock.Fd());
    }

    void start()
    {
        std::string clientip;
        uint16_t clientport;
        while (true)
        {
            pthread_t tid;
            int sock = _listensock.Accept(&clientip, &clientport);
            ThreadData *td = new ThreadData(sock, clientip, clientport, this);
            pthread_create(&tid, nullptr, ThreadRoutine, td);
        }
    }

    static void *ThreadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->_tsvr->ServiceIO(td->_sock, td->_clientip, td->_clientport);
        delete td;
        return nullptr;
    }

    void ServiceIO(int sock, std::string &clientip, uint16_t clientiport)
    {
        std::string inbuffer; // 读取报文缓冲区
        while (true)
        {
            // 1.读取报文并获得有效载荷
            std::string package;
            int n = readPackage(sock, inbuffer, &package);
            if (n == -1)
                break;
            else if (n == 0)
                continue;
            package = removeHeader(n, package);
            // 2.请求反序列化
            Request req;
            req.Deserialize(package);
            req.Print();
            // 3.处理请求
            Response res = _func(req); // 业务逻辑!
            res.Print();
            // 4.相应序列化
            std::string send_string;
            res.Serialize(&send_string);

            // 5.添加报头并发送报文
            send_string = addHeader(send_string);
            send(sock, send_string.c_str(), send_string.size(), 0);
        }
        close(sock);
    }

    private:
    uint16_t _port;
    Sock _listensock;
    func_t _func; // 网络服务方法--进行计算
};

任务执行实体

服务端主线程初始化进行初始化操作后,服务端每获取一个客户端的连接后,会为其创建一个新线程,由这个新线程来担任任务执行的实体。主线程只做监听和获取连接的操作,新线程来完成计算器的任务。其中,新线程会调用ThreadRoutine函数执行任务。

ThreadData类的作用

由于完成任务的是新线程,因此新线程需要得到完成任务所需的参数,包括进行数据传输的套接字文件描述符、客户端IP地址、客户端端口号、服务端类的指针等。需要服务端类的指针的原因是,任务执行函数ServiceIO函数是服务端类的成员函数,需要使用服务端类的指针调用该函数。其中,ServiceIO函数中的数据计算作为网络服务方法在创建类对象时传入。

启动服务端

首先,启动服务端前,对输入的命令行参数进行纠错处理,其次,创建服务端类后,需要传入数据计算方法和要绑定的端口,具体代码如下:

Response calculate(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._exitcode = 1;
        else
            resp._result = req._x / req._y;
        break;
    case '%':
        if (req._y == 0)
            resp._exitcode = 2;
        else
            resp._result = req._x % req._y;
        break;
    default:
        resp._exitcode = 3;
        break;
    }

    return resp;
}

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << "port\n" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr(new TcpServer(calculate, port));
    tsvr->initServer();
    tsvr->start();
    return 0;
}

编写客户端类

和服务端相同,将客户端也封装成类,提供客户端初始化成员函数,提供客户端运行函数,具体代码实现如下:

class TcpClinet
{
public:
    TcpClinet(std::string serverip, uint16_t serverport) : _serverip(serverip), _serverport(serverport)
    {
    }
    void initClient()
    {
        _sock.Socket();
        int n = _sock.Connect(_serverip, _serverport);
        if (n == -1)
        {
            LogMessage(Fatal, "connect socket error:", strerror(errno));
            exit(-1);
        }
    }

    void start()
    {
        std::string buffer;
        while (true)
        {
            Request req;
            // 1. 获取操作数和操作符
            int x;
            int y;
            char op;
            std::cout << "Pleas enter x>";
            std::cin >> req._x;
            std::cout << "Pleas enter y>";
            std::cin >> req._y;
            std::cout << "Pleas enter op>";
            std::cin >> req._op;
            // 2. 将结构体数据序列化
            std::string req_string;
            req.Serialize(&req_string);
            req.Print();
            // 3. 添加报头
            req_string = addHeader(req_string);
            // 4. 发送报文
            send(_sock.Fd(), req_string.c_str(), req_string.size(), 0);
            // 5. 接收相应
            std::string package;
            int n = 0;
            do
            {
                n = readPackage(_sock.Fd(), buffer, &package);
                if (n < 0)
                    break;
            } while (n == 0);
            if (n < 0)
                break;
            // 6. 去掉报头
            package = removeHeader(n, package);

            // 7. 反序列化
            Response resp;
            resp.Deserialize(package);
            resp.Print();
        }
    }

private:
    Sock _sock;
    std::string _serverip;
    uint16_t _serverport;
};

客户端运行

使用start函数运行客户端后,先接收用户输入的计算数据,然后封装成Request类的结构化数据,对其序列化发送,接收数据后反序列化转换成Response类的结构体化数据输出。

启动客户端

和服务端相同,首先,启动服客户端前,对输入的命令行参数进行纠错处理,具体代码如下:

void Usage(std::string proc)
{
    std::cout << "Usage:\n\t" << proc << "serverip serverport\n"
              << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    std::unique_ptr<TcpClinet> tcvr(new TcpClinet(serverip, serverport));
    tcvr->initClient();
    tcvr->start();
    return 0;
}

程序测试

首先启动服务端,并绑定端口号为8083:

image-20231104193352884

启动客户端,并输入服务端的IP地址和端口号:

image-20231104193445281

在客户端分别输入左操作数、右操作数、操作符,查看结果:

image-20231104193615224

再尝试进行除法运算:

image-20231104193709839

由于除数为0,因此返回码为1。

说明: 该程序只有在用户输入的数据符合协议时,才具有健壮性,除此之外可能存在问题,比如,如果客户端和服务器分别在不同的平台下运行,在这两个平台下计算出请求结构体和响应结构体的大小可能会不同,此时就可能会出现一些问题。虽然当前代码存在很多潜在的问题,但这个代码能够让我们直观地体会到协议的作用,这里将其当作一份示意性代码就行了。

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

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

相关文章

python把Word题库转成Excle题库

又到了一年一度的背题时刻&#xff0c;但是收到的题库是Word版的&#xff0c;页数特别多 话不多说&#xff0c;上代码&#xff0c;有图有真相&#xff0c;代码里面备注的很详细 # 导入所需库 import csv import os import refrom docx import Document from win32com import c…

【数据结构】数组和字符串(十四):字符串匹配1:朴素的模式匹配算法(StringMatching)

文章目录 4.3 字符串4.3.1 字符串的定义与存储4.3.2 字符串的基本操作4.3.3 模式匹配算法1. 算法原理2. ADL语言3. 伪代码4. C语言实现5 时间复杂度 4.3 字符串 字符串(String)是由零个或多个字符(char)顺序排列组成的有限序列&#xff0c;简称为串。例如 “good morning”就是…

共焦显微镜使用

x.1 细胞培养 x.2 样品制备 以细菌为例&#xff0c;我们使用荧光染色细菌&#xff0c;静置15分钟。 15分钟后我们使用实验室的专用培养皿&#xff0c;选择吸收100uL的溶液滴在在培养皿中心。 x.3 显微镜使用 我们按照1, 2, 3, 4的顺序打开显微镜&#xff0c; 打开电脑&…

降级python

起因&#xff1a; python版本过高不能下载一个包&#xff0c;需要降级 首先使用 python --version 查看python版本 然后conda install python3.10 python3.10会下载到这个目录下&#xff08;这个千万别找错&#xff09; 然后更换路径 alias python/home/zky/.conda/envs/c…

【MAC+IP】以太网帧格式

图片出自&#xff1a;https://info.support.huawei.com/info-finder/encyclopedia/zh/MTU.html

FFmpeg 硬件加速视频转码指南

基于 Windows 下演示&#xff0c;Linux 下也可以适用。 所使用 ffmpeg 版本为 BtbN 编译的 win64-gpl 版&#xff08;非 gpl-share&#xff09;&#xff0c;项目地址&#xff1a;BtbN / FFmpeg-Builds 也可以使用 gyan.dev 编译的 git-full 版&#xff0c;地址&#xff1a;gyan…

053基于web+springboot的宠物咖啡馆平台的设计与实现

欢迎大家关注&#xff0c;一起好好学习&#xff0c;天天向上 文章目录 一项目简介技术介绍 二、功能组成三、效果图四、 文章目录 一项目简介 本基于Spring Boot的宠物咖啡馆平台的设计与实现有管理员和用户以及看护师三个角色。用户功能有个人中心&#xff0c;咖啡菜品管理&a…

微信视频号直播间引流粉丝脚本软件实操教学,文章加视频演示详细教学方法

我是小刘&#xff0c;第一我要讲的是为什么要做视频号&#xff1f; 今天我们来去演示的是视频号直播间引流脚本的一个教学&#xff0c;我这边用文章加视频讲解的方法来分享给大家。关于引流有两点&#xff0c;1 就是自媒体推广&#xff0c;2就是脚本引流&#xff0c;我今天给大…

定时任务场景下的代码审查:continue和return的滥用可能引发潜在bug

文章目录 前言for 循环中的continue&#xff0c;break和return实际业务中的滥用总结写在最后 前言 在最近的代码审查中&#xff0c;有帮忙审查了组里一个刚毕业1年不到的应届生&#xff0c;发现他写的其中一段代码将for循环中的break、continue、return滥用&#xff0c;导致了…

口碑超好高质量经典小说,收获无数赞誉,完结多年还是热门之作

经典好文&#xff0c;小郑来为您推荐五本值得一读的好书&#xff0c;这些书籍不仅可以让您的时间不再无聊&#xff0c;还能让您在阅读中获得更多的知识和乐趣。 《传说管理局》 这本书的世界观严谨且庞大&#xff0c;充满了科幻和玄幻元素。如果您喜欢科幻和玄幻类型的书籍&am…

力扣:149. 直线上最多的点数(Python3)

题目&#xff1a; 给你一个数组 points &#xff0c;其中 points[i] [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱…

记一次heapdump泄漏获取服务器权限

文章目录 一、漏洞原因二、漏洞利用三、漏洞进一步利用1、工具下载2、通过关键字查询3、通过配置redis的默认账号和密码进行登录4、添加定时计划任务,进行反弹shell5、成功获取服务器的shell补充四、总结五、免责声明一、漏洞原因 扫描目录发现某个spring框架存在大量泄露信息…

DBeaver关闭代码的提示

在DBeaver中会遇到如下现象&#xff0c;很烦&#xff0c;怎么取消这个提示框呢&#xff1f; 解决方案&#xff1a;

第二章 02Java基础-数据类型、标识符、键盘录入

文章目录 前言一、数据类型二、标识符三、键盘录入总结前言 今天我们学习Java基础,数据类型、标识符、键盘录入 一、数据类型 1.数据类型大体上可以分为两类,一类是基本数据类型,另外一类是引用数据类型。今天我们学习基本数据类型。 2.基本数据类型可以分为四类八种,整…

一看就懂,原来这就是计算机网络

引言 计算机网络&#xff0c;听上去很专业的样子&#xff0c;其实我们可以换种思维来理解。 正文 什么是计算机网络&#xff1f; 下图就是计算机网络&#xff0c;所有能联网的设备连接在一起就组成了互联网 计算机网络有什么用&#xff1f; 计算机网络的作用就是用于设备之…

VueX mapState、mapGetters、mapActions和mapMutaions 的使用

一、mapState和mapGetters 如果我们想要读取VueX中的State数据的Getters数据时&#xff0c;需要使用$store.state.数据名 和 $store.getters.数据名。 当我们State和getters中的数据多了以后&#xff0c;书写会很麻烦&#xff1a; 如果我们想要使用方便可以配置计算属性来简化…

Arduino设置SoftwareSerial缓冲区大小

SoftwareSerial的缓冲区大小设置 概述修改缓冲区的大小实验 概述 新的Arduino的ESP8266软串口的缓冲区原来老的库中有宏定义可以用来修改接收和发送缓冲区的大小。在现在新的库中已经没有这个设置了&#xff0c;那怎么才能修改缓冲区的大小哪&#xff1f; 修改缓冲区的大小 …

5.2用队列实现栈(LC225-E)

算法&#xff1a; 其实这道题不用像上一道题一样&#xff0c;用两个队列实现栈。 由于队列的数据结构特性。用一个队列就可实现栈。 难点还是在出队的时候&#xff1a; 比如队列[1,2,3]&#xff0c;要模拟一个栈入栈就是直接append&#xff08;其实就是C中的push&#xff0…

creating server tcp listening socket 127.0.0.1:6379: bind No error

window下启动redis服务报错&#xff1a; creating server tcp listening socket 127.0.0.1:6379: bind No error 解决方案如下按顺序输入如下命令即可连接成功 redis-cli.exeshutdownexit运行&#xff1a;redis-server.exe redis.windows.conf shutdown出现以下错误&#xff…

数据结构之队的实现

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…