网络协议栈应用层的意义(内含思维导图和解析图通俗易懂超易理解)

news2024/9/23 3:19:34

绪论​:
“节省时间的方法就是全力以赴的将所要做的事情完美快速的做完,不留返工重新学习的时间,才能省下时间给其他你认为重要的东西。”
本章主要讲到OSI网络协议栈中的应用层的作用和再次在应用层的角度理解协议的具体意义,以及序列化、反序列化和解决Tcp字节流边界问题的方法,最后通过一个实操题来具体的看到应用层所要完成的操作(其中包含了Socket网络编程和多线程内容没看的一定要提前看喔)

请添加图片描述
话不多说安全带系好,发车啦(建议电脑观看)。

OSI定制的七层网络协议栈:
在这里插入图片描述

1.应用层

1.1再谈“协议”

之前所写的协议,本质就是一个约定,而现在再把这个约定实体化:

现在通过网络版计数器,来更好的理解:
为了网络上更便利的发送,要进行序列化和反序列化操作

  1. 序列化:将协议对应的结构化数据,转换成“字符串”字节流(目的:方便网络发送)
    也就是将多条信息打包成一条信息(一个字符串)
  2. 反序列化:将“字符串”字节流,转换成结构化数据(为上层业务随时提前有效字段)
    也就是再把这条信息再分回多条
    在这里插入图片描述
    协议就是发送的信息的结构(上方发送来的信息的原本结构就是那三条信息)

对于这些信息就可以把协议描述成一个结构体,并且双方对这个协议结构体都提前约定好的
客户端和服务端用同一个结构体(数据类型)对特定的字段进行解释,那这就是计算机层面上的协议(计算机间的约定)
在这里插入图片描述
而在网络中不直接传递整个协议结构:而是通过序列化(直接把结构体地址传过去:也就是char*)后在网络中传送,
到对方后再进行反序列化操作解析(通过强转为协议类型后选择对应的数据填充协议结构:(sturct Message*)p->name …)。
这样双方就能进行序列化和反序列的操作(也就是协议的目的)

应用层在不同的系统,平台上会有很多种协议结构,这样就不能保证所有平台双方统一正确的读取(平台差异),所以经过序列化和反序列化就能很好的解决这个问题(提前约定了协议就能获取/发送所要的数据)。

1.2协议定制

在应用层角度来看网络:
对于用户和服务端来说他需要有请求和应答协议

  1. 用户需要有: 请求(发送)协议
  2. 服务端需要有:应答协议

其中我们通过send/write、read/recv,来进行数据的发送和接收。
而请求、应答也是在这些函数的基础上来实现的
在这里插入图片描述
send/write、read/recv本质其实是拷贝函数,将用户写的数据拷贝到发送缓冲区/将接受缓冲区拷贝到用户数据(而具体要什么时候发送给server,这是由TCP协议决定(内核决定))

TCP传输控制协议:TCP实际通信的时候,就是双方OS之间进行通信!

在发送用户空间:你认为你发了多少字节,但不一定会收多少字节 (要看双发缓冲区容量由TCP决定(具体下面写到tcp协议内部时就能清楚的知道,这里先记住即可)) ,这也表明了TCP是面向字节流的(不像UDP是不会出现这样问题的,因为其是面向数据报的(一次发送一个整个数据报文))。

所以在TCP中可能一次发送中报文无法发完,对此我们需要明确报文和报文之间的边界,才能防止读取时并没有读完就使用的错误

应用层如何解决TCP字节流边界问题的方法:

通过对协议序列化的字符串上加上特定字符来当作边界:
在序列化后的字符串前加上len\n来当其的边界和开头来区分报文


当Request请求协议写成:

class Request
{
	//...
private:
    int _data_x;
    int _data_y;
    char _oper;// + - *
};

序列化后的字符串:x op y
再对字符串添加特定字符:“len\nx op y” (len\n)

其中len表示的是后面数据的长度,\n用来区分前后len与数据,通过len和\n这样就能避免数据未读完的情况。

因为:只有读到了\n通过\n前面的len才能确定后面的数据长度继续往后读,反之没读到\n就一直读到\n为止才开始往后读(len是个数字他可能是数据也可能是加的特定字符)。

例:

缓冲区中的报文可能是:“len\nx op y”“len\nx op y”“len\nx op y”“len\nx op y”.当读取到第一个len后面的\n就表示找到了一个报文)
也可能是:“len\nx op y”“len\nx op y”“len\nx op y”"len\nx “(此时就看最后和正常的字符串比较是少了"op y”,此时就能通过len来知道字符串是不够的!!)

但这样打印出来的字符可能不好看(会直接全部在同一行)
所以一般会再在每个字符串后再加上\n来让打印换行:

“len\nx op y\n”,这样就即美观又能防止数据未读完的情况

(其中\n不属于报文的一部分,仅仅只是个约定)

第一个就变成:len\nx op y\nlen\nx op y\nlen\nx op y\nlen\nx op y \n
第二个就变成:len\nx op y\nlen\nx op ylen\nx op ylen\nx \n
(真正发送的报文,此时将""去了)

通过这样的方式就能实现将一个个报文区分出来,获取所要的数据


Response成员有:result(结果)、code(状态码)
同理在Response回复协议中序列化的字符串也写成:“len\nresult code\n”


对此通过上方就能理出应用层角度的通信过程:
客户端与服务器:
客户端发送Request请求,服务器返回Response应答

在下面模拟实现中,把序列化、反序列化都分成了两份:

  1. 处理 真正的数据(x op y)
  2. 添加报头(len\n真正的数据 \n)

也就是对应函数:

  1. 处理真正数据的序列化:Serialize、反序列化:DeSerialize
  2. 添加报头信息:Encode、除去报头信息:Decode

若实操有点难看,建议放进自己的如Vscode写代码的软件中

实操计算器,具体如下:

TcpClientMain.cc:
客户端的主要逻辑,具体逻辑已用数字标好了:

#include "Protocol.hpp"
#include "Socket.hpp"
#include <sys/types.h>
#include <sys/socket.h>
#include <ctime>
#include <cstdlib>
#include <unistd.h>
#include <memory>
#include <iostream>
using namespace std;
using namespace Protocol;

int main(int argc, char *argv[])
{
//通过main参数输入ip和port(此处默认会,不会请自行搜索main参数如何使用)
    if (argc != 3)
    {
        cout << "Usage:" << argv[0] << "serverip serverport" << endl;
        return 0;
    }
    string serverip = argv[1];
    uint16_t serverport = stoi(argv[2]);


    Socket *conn = new TcpSocket();//建立链接
    if (!conn->BuildConnectSocketMethod(serverip, serverport)) // BuildConnectSocketMethod : GreateSocket、
    {
        cerr << "connect" << serverip << ":" << serverport << " failed" << endl;
        return 0;
    }
    cout << "connect" << serverip << ":" << serverport << " success" << endl;

    // unique_ptr<Factory> factory = make_unique<Factory>();
    unique_ptr<Protocol::Factory> factory(new Protocol::Factory();
//创建工厂变量,工厂将会在协议Protocol类中实现(在后面的源文件中)

    srand(time(nullptr) ^ getpid());//生成随机数,来进行计算
    string opers = "+-*/%^=!";// +-*/ ,其余是故意写的来模仿错误情况 
    while (true)
    {
        int x = rand() % 100;//限制大小
        usleep(rand() % 7777);
        int y = rand() % 100;
        char oper = opers[rand() % opers.size()];//生成计算方法

        shared_ptr<Protocol::Request> req = factory->BuildRequest(x, y, oper);//通过工厂创建请求

        // 1. 对req进行序列化
        string req_str;
        req->Serialize(&req_str);
        // for print
        string testptring = req_str;
        testptring += " ";
        testptring += "= ";

        // 2. 拼接报头
        req_str = Encode(req_str);

        // 3. 发送
        conn->Send(req_str);

        string resp_str;
        while (true)
        {
            // 4. 接收响应
            if(!conn->Recv(&resp_str, 1024)) break;

            // 5. 除去报头
            string message;
            if (!Decode(resp_str, &message)) continue;//错误说明短了所以继续再读

            // 6. 得到了数据"result code",将数据结构化
            auto resp = factory->BuildResponse();
            resp->DeSerialize(message);

            //7. 打印结果:
            cout << testptring << resp->GetResult() << "[" << resp->GetCode() << "]" << endl;
            break;
        }
        sleep(1);
    }
    conn->CloseFd();

    return 0;
}

TcpServerMain.cc:
服务器主要执行处理获取的数据:

#include "Protocol.hpp"
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Calculate.hpp"
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory> 
#include <pthread.h>

using namespace std;
using namespace CalCulateNS;
using namespace Protocol;

//服务器接收到数据,对数据进行处理方法
string HandlerRequest(string & inbufferstream,bool* error_code)
{
    *error_code = true;//数据没问题,只是读取不够,所以返回true
    //计算机对象
    Calculate calculate;
    //构建相应对象
    unique_ptr<Factory> fact(new Factory());
    auto req = fact->BuildRequest();

    string message;//存发来的信息

    string total_resp_string;
     while(Decode(inbufferstream,&message))//只有解析成功才往后
    {
        //反序列化
        if(!req->DeSerialize(message))
        {
            *error_code = false;//不可容忍的错误!
            return string();
        }

        //处理数据,并将处理好的数据以回复协议的形式返回
        auto resq = calculate.Cal(req);
        
        //5. 得到resq,再序列化准备发回给客户端
        string send_str;
        resq->Serialize(&send_str);
        //序列化后得到字符串 "result code"
        
        //6. 拼接len\n,形成字符串级别的相应报文
        send_str = Encode(send_str);
        
        // 7. 发送,将数据处理好存进total_resp_string 中返回到TCPServer.hpp中
        total_resp_string += send_str;
    }
    return total_resp_string;
}
 
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cout << "Usage:" << argv[0] << " port" << endl;
        return 0;
    }

    uint16_t localport = stoi(argv[1]);

    unique_ptr<TcpServer> svr(new TcpServer(localport, HandlerRequest));
    svr->loop();

    return 0;
}

服务器的主要逻辑在TCPServer.hpp内:
结合之前学的线程,让线程执行任务ThreadRun:

#pragma once
#include "Socket.hpp"
#include <iostream>
#include <memory>
#include <functional>
using namespace std;

using fun_t = std::function<string(string &,bool* error_code)>;

class TcpServer;
class ThreadData
{
public:
    ThreadData(TcpServer* tcp_this,Socket* sockp):_this(tcp_this),_sockp(sockp)
    {}
    TcpServer* _this;
    Socket* _sockp;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, fun_t request) : _port(port), _listensocket(new TcpSocket()), _handler_request(request)
    {
        _listensocket->BuildListenSocketMethod(port, backlog);
    }

    static void* PhreadRun(void*argv)//PthreadRun是个类的成员方法所以用static,不要this指针才能满足void* (*) (void*)
    {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(argv);
        string bufferin; 
        while(true)
        {
            bool ok = true;//用来确定是否出错
            //读取数据,不关心数据,只进行读
            //1. 接收信息
            if(!td->_sockp->Recv(&bufferin,1024)) 
            {
                break;
            }
            //2. 处理报文数据,对获取数据进行反序列化处理后得到结果,再序列化发送回去
            string send_string = td->_this->_handler_request(bufferin,&ok);//回调不仅会出去,还会回来!
            //读发送数据,不关心数据,只进行发送
            if(ok)
            {
                //3. 发送数据
                if(!send_string.empty())
                {
                    td->_sockp->Send(send_string);
                }
            }
            else
            {
                break;
            }

        }
        td->_sockp->CloseFd();
        delete td->_sockp;
        delete td;
        return nullptr;
    }

    void loop()
    {
        for (;;)
        {
            string peerip;
            uint16_t peerport;
            Socket *newsock = _listensocket->AcceptConnection(&peerip, &peerport);//会返回Socket*
            if (newsock == nullptr)
            {
                cout << "AcceptConnection fail";
                continue;
            }
            cout << "获取一个新连接,sockfd:" << newsock->GetSockfd() << " client info:" << peerip << ":" << peerport;
            pthread_t tid;
            ThreadData* td = new ThreadData(this,newsock);
            pthread_create(&tid, nullptr, PhreadRun, td);
        }
    }
    ~TcpServer()
    {
        delete _listensocket;
    }

private:
    uint16_t _port;
    Socket *_listensocket;
public:
    fun_t _handler_request;
};

协议的实现:
Protocol.hpp:

#pragma once
#include <iostream>
#include <memory>
using namespace std;
namespace Protocol
{
    const string ProtSep = " ";
    const string LineBreakSep = "\n";

//添加报头信息
    string Encode(const string&message)//"len\nx op y\n"
    {
        string len = to_string(message.size());//计算数据的长度,然后再把这个长度变成字符串
        string package = len + LineBreakSep + message + LineBreakSep;//将报头信息添加进去,还包括了两个\n
        return package;
    }

//其中注意的是package不一定是一个完整的报文,他可能长了也可能短了,对此我们要进行处理!
//短了:"len"、"len\nx" 
//长了:"len\nx op y\n""len"、...
    bool Decode(string&package,string *message)
    {
        auto pos = package.find(LineBreakSep);//首先找到\n,来确定len,若\n找不到则表示短了!
        if(pos == string::npos) return false;//\n都找不到直接返回错误!

//到了此处至少能取出len了!
        string lens = package.substr(0,pos);//取出字符串len
        int len =  stoi(lens);//将len转换成整形

//取出len后,计算长度,判断传递进来的字符串package和实际字符串长度
        int total = lens.size() + len + 2 * LineBreakSep.size();//字符串实际长度
//若
        if(total > package.size()) return false;//如果传递进来的长度小于实际长度则一定未完全将字符串传递过来!

        //否则则直接取出len长度的实际信息的字符串即可!
        *message = package.substr(pos + LineBreakSep.size() , len);
        //最后把取出的删除!
        package.erase(0,total);
        return true;
    }

//请求协议
    class Request
    {
    public:
        Request():_data_x(0),_data_y(0),_oper(0)
        {}
        Request(int x, int y, char op) : _data_x(x), _data_y(y), _oper(op)
        {}
        void Debeg()
        {
            cout << "_data_x: " << _data_x << endl;
            cout << "_data_y: " << _data_y << endl;
            cout << "_oper: " << _oper << endl;
        }
        void Inc()
        {
            _data_x++;
            _data_y++;
        }

        // 结构化数据 -> 字符串
        bool Serialize(string *out)
        {

            *out = to_string(_data_x) + ProtSep + _oper + ProtSep + to_string(_data_y);
            return 0;
        }

        bool DeSerialize(string &in) //"x op y"
        {
            auto left = in.find(ProtSep);
            if (left == string::npos)
                return false; // 表示没找到!
            auto right = in.rfind(ProtSep);
            if (right == string::npos)
                return false; // 表示没找到!

            _data_x = stoi(in.substr(0, left));
            _data_y = stoi(in.substr(right + ProtSep.size()));
            string oper = in.substr(left + ProtSep.size(), right - (left + ProtSep.size()));
            if (oper.size() != 1)
                return false;

            _oper = oper[0];
            return true;
        }
        int GetX(){return _data_x;}
        int GetY(){return _data_y;}
        char GetOper(){return _oper;}

    private:
        //"x op y\n",以\n结尾,当读取到\n表示当前报文读完了
        // len是报文字描述字段
        //"len"\n"x op y",其中len可以理解成报文、后面的x op y理解成有效载荷
        // 并且len 后面加\n进行分隔,len就表述了后面有效载荷的数据长度(也就是要读取的长度)
        //"len\nx op y"
        int _data_x;
        int _data_y;
        char _oper; // + - *
    };

//回应协议
    class Response
    {
    public:
        Response():_result(0),_code(0)
        {}
        Response(int result, int code) : _result(result), _code(code)
        {}
        bool Serialize(string *out)
        {
            *out = to_string(_result) + ProtSep + to_string(_code);
            return true;
        }

        bool DeSerialize(string &in)
        {
            auto pos = in.find(ProtSep);
            if (pos == string::npos)
                return false; // 表示没找到!

            _result = stoi(in.substr(0, pos));
            _code = stoi(in.substr(pos + ProtSep.size()));
            return true;
        }
        void SetResult(int result) {_result = result;}
        void SetCode(int code) {_code = code;}

        int GetResult(){ return _result; }
        int GetCode(){ return _code; }
    private:
        int _result; 
        int _code;
    };


    // 简单的工厂模式,通过工厂模式构造出对应的协议类!
    class Factory
    {
    public:
        shared_ptr<Request> BuildRequest()
        {
            shared_ptr<Request> req = make_shared<Request>();
            return req;
        }

        shared_ptr<Request> BuildRequest(int x, int y, char op)
        {
            shared_ptr<Request> req = make_shared<Request>(x, y, op);
            return req;
        }

        shared_ptr<Response> BuildResponse()
        {
            shared_ptr<Response> resp = make_shared<Response>();
            return resp;
        }

        shared_ptr<Response> BuildResponse(int result, int code)
        {
            shared_ptr<Response> resp = make_shared<Response>(result, code);
            return resp;
        }
    };
}

所实现的功能,计算器:

#pragma once

#include <memory>
#include "Protocol.hpp"
#include <iostream>

namespace CalCulateNS
{
    enum
    {
        Success = 0,
        DivZeroErr,
        ModZeroErr,
        Unknown
    };

    class Calculate
    {
    public:
        Calculate() {}

        shared_ptr<Protocol::Response> Cal(shared_ptr<Protocol::Request> req)
        {
            shared_ptr<Protocol::Response> resp = fact->BuildResponse();
            resp->SetCode(Success);
            switch (req->GetOper())
            {
                case '+':
                    resp->SetResult(req->GetX() + req->GetY());
                    break;
                case '-':
                    resp->SetResult(req->GetX() - req->GetY());
                    break;
                case '*':
                    resp->SetResult(req->GetX() * req->GetY());
                    break;
                case '/':
                {
                    if (req->GetY() == 0)
                    {
                        resp->SetCode(DivZeroErr);
                    }
                    else
                    {
                        resp->SetResult(req->GetX() / req->GetY());
                    }
                }
                break;
                case '%':
                {
                    if (req->GetY() == 0)
                    {
                        resp->SetCode(ModZeroErr);
                    }
                    else
                    {
                        resp->SetResult(req->GetX() % req->GetY());
                    }
                }
                break;
                default:
                    resp->SetCode(Unknown);
                break;
            }

            return resp;
        }

        ~Calculate() {}

    private:
        shared_ptr<Protocol::Factory> fact;
    };
}

套接字通信原理,Socket.hpp:

#pragma once
#include <iostream>
//网络常用的四个头文件
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> 
#include <unistd.h>
#include <string.h>
#include <string>
using namespace std;
#define CONV(addrptr) ((struct sockaddr*)addrptr)

const static int sockdefault = -1;
const static int backlog = 5;

enum{
    SocketError = 1,
    BindError,
    ListenError 
};
//封装一个基类,socket接口类
//模板方法,设计模型
class Socket
{
public:
    virtual ~Socket(){}
    virtual void GreateSocket() = 0;
    virtual void BindSocketOrDie(uint16_t port) = 0;
    virtual void ListenSocketOrDie(int backlog) = 0;//       int listen(int sockfd, int backlog);
    virtual Socket* AcceptConnection(std::string* sockip,std::uint16_t* sockport) = 0;//输出型参数
    virtual bool ConnectServer(std::string& sockip,std::uint16_t sockport) = 0;
    virtual int GetSockfd() = 0;
    virtual void SetSockfd(int sockfd) = 0;
    virtual void CloseFd() = 0;
    virtual bool Recv(string* buffer, int size) = 0;
    virtual bool Send(string& buffer) = 0;
public:

    void BuildListenSocketMethod(uint16_t port,int backlog)
    {
        GreateSocket();
        BindSocketOrDie(port);  
        ListenSocketOrDie(backlog);
    }    
    bool BuildConnectSocketMethod(std::string& sockip,std::uint16_t& sockport)
    {
        GreateSocket();
        return ConnectServer(sockip,sockport);
    }
    void BuildNormalSocketMethod(int sockfd)
    {
        SetSockfd(sockfd);
    }
};

class TcpSocket : public Socket
{
public:
    TcpSocket(int sockfd = sockdefault):_sockfd(sockfd)
    {} 
    ~TcpSocket()
    {}

    void GreateSocket() override
    {   
        _sockfd = ::socket(AF_INET,SOCK_STREAM,0);
        if(_sockfd < 0) 
        {
            exit(SocketError);
            cout << "GreateSocket failed" << endl;
        }
    }

    void BindSocketOrDie(uint16_t port) override
    {
        struct sockaddr_in local;
        memset(&local,0,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;//不指定ip
        local.sin_port = htons(port);

        int n = ::bind(_sockfd,CONV(&local),sizeof(local));
        if(n < 0)
        {
            exit(BindError);
            cout << "BindError failed" << endl;
        } 
    }
    //int listen(int sockfd, int backlog);
    void ListenSocketOrDie(int backlog) override   
    {
        int n = ::listen(_sockfd,backlog);
        if(n < 0)
        {
            exit(ListenError);
            cout << "ListenError failed" << endl;
        } 
    }

    Socket* AcceptConnection(std::string* sockip,std::uint16_t* sockport) override
    {
        struct sockaddr_in addr;
        socklen_t len = sizeof(addr);
        int newsocket = accept(_sockfd,CONV(&addr),&len);
        if(newsocket < 0) return nullptr;

        Socket* s = new TcpSocket(newsocket);

        *sockport = ntohs(addr.sin_port); 
        *sockip = inet_ntoa(addr.sin_addr); 
        return s;
    }
    
    bool ConnectServer(std::string& sockip,std::uint16_t sockport) override
    {
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = inet_addr(sockip.c_str());
        server.sin_port = htons(sockport);

        socklen_t len = sizeof(server);

        int n = connect(_sockfd,CONV(&server),len);//你这是啥??
        //你是一个客户端,你为什么要accept?可能是写错了
        //改下为connect,应该就没啥问题了   还有什么问题吗?我先试下 好的,我先下了啊哈
        if(n == 0) return true;
        else return false;
    }

    int GetSockfd()
    {
        return _sockfd;
    }
    void SetSockfd(int sockfd)
    {
        _sockfd = sockfd;
    }

    void CloseFd() override
    {
        if(_sockfd > sockdefault) ::close(_sockfd);
    }

    bool Recv(string* buffer ,int size) override
    {
        char bufferin[size];
        size_t n = recv(_sockfd,bufferin,size-1,0);
        if(n > 0)
        {
            bufferin[n] =0;
            *buffer += bufferin;//此处是+=故意让其拼接!
            return true;
        }
        return false;
    }

    bool Send(string& buffer)override
    {
        send(_sockfd,buffer.c_str(),buffer.size(),0);
        return true;
    }
    
private:
    int _sockfd;
};

1.3成熟的序列化和反序列化方案:

常见的序列化协议

  1. json:允许采用 {"key ", “value”} 的方式将信息组织起来
  2. protobuf
  3. xml

下面我们将使用json

centos 7.9安装JSON流程:
sudo yum install jsoncpp-devel
ubuntu:
sudo apt install list libjsoncpp-dev

序列化:

Json::Value root;//Json::Value类型
//像map中的[]的使用一样
root["k1"] = 1;
root["k2"] = "string";
Json::FastWrite writer;
string s = wirter.write(root);//序列化生成字符串

反序列化:

bool Deserialize(string &in)
{
	Json::Value root;
	Json::Reader reader;
	
	bool res = reader.parse(in,root);
	//第一个参数是一个流,从in字符串流中获取数据反序列化给到root
	if(res)
	{
		_result = root["result"].asInt();//asInt转化成整形
		_code = root["code"].asInt();
	}
	return res;
}


当我们写完这些代码后,回过去看ISO七层模型中的顶上三层,应用、表示、对话层
在这里插入图片描述
不难发现他们分别对应着
会话层对应着:Socket套接字实现的连接和断开操作(connect、accept)

表示层对应着:协议的定制以及序列化和反序列化的操作(Protocol、Serialize)

应用层对应着:最终所要实现的功能(Calculate)


本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量计算机网络细致内容,早关注不迷路。

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

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

相关文章

#Datawhale AI夏令营第4期#多模态大模型Task3

写在前面的碎碎念》 为时一个礼拜的学习&#xff0c;即将结束了。回顾这一个礼拜&#xff0c;因此这次的任务较难&#xff0c;大部分的时间都花在跑模型上了&#xff0c;跑一次一天就没了&#xff0c;所以基本没有很好的去尝试优化上分&#xff0c;一个礼拜&#xff0c;差不多…

微商城系统 goods.php SQL注入漏洞复现

0x01 产品简介 微商城系统,又称微信商城系统,是基于微信等社交平台构建的一种小型电子商务系统。该系统融合了社交媒体的互动性和网络商城的交易功能,为商家提供了一个集商品展示、在线交易、营销推广、用户管理、数据分析等功能于一体的综合性电商平台。系统充分利用了微信…

【SecureLock】藏起你的秘密文件!

我们都知道&#xff0c;在 Windows 中可以右键文件夹&#xff0c;选择”属性“&#xff0c;勾选”隐藏“来实现隐藏某个文件夹。 我们还知道&#xff0c;在 Windows 中可以选择勾选 ”显示隐藏的项目和文件夹“&#xff0c;来使上述方法变得形同虚设。 本工具就是用于解决以上…

使用Linux内核自带的V4L2设备驱动 采集图像

一、定义 V4L2代表Video for Linux Two&#xff0c;它是Linux内核的一部分&#xff0c;提供了一种统一的方式来访问各种视频输入/输出设备&#xff0c;如摄像头、电视卡等。 二、工作流程&#xff08;重点&#xff09; 打开设备&#xff0d;> 检查和设置设备属性&#xf…

Elasticsearch-关键词随机查询(8.x)

目录 一、查询语句 二、Java代码实现 基础介绍&#xff1a; ES自定义评分机制:function_score查询详解-阿里云开发者社区ES自定义评分机制:function_score查询详解https://developer.aliyun.com/article/1054571 开发版本详见&#xff1a;Elasticsearch-经纬度查询(8.x-半径…

面向对象程序设计(C++)之 vector(初阶)

1. vector 的构造 vector 需要显式实例化类模版&#xff0c;在创建 vector 类型的容器时可以直接创建&#xff0c;也可以进行初始化&#xff0c;例如 v2 &#xff0c;也可以使用迭代器的方式创建&#xff0c;具体关于更多vector的知识: vector //模版类只能显式实例化 vector&l…

Linux ubuntu 24.04 安装运行《帝国时代3》免安装绿色版游戏,解决 “Could not load DATAP.BAR”等问题

Linux ubuntu 24.04 安装运行《帝国时代3》游戏&#xff0c;解决 “Could not load DATAP.BAR" 等问题 《帝国时代 3》是一款比较经典的即时战斗游戏&#xff0c;伴随了我半个高中时代&#xff0c;周末有时间就去泡网吧&#xff0c;可惜玩的都是简单人机&#xff0c;高难…

构建具有音频功能的中英翻译器:一个Python应用程序的旅程

在当今的全球化世界中&#xff0c;语言翻译工具变得越来越重要。作为一名软件开发者&#xff0c;我最近完成了一个有趣的项目&#xff1a;一个结合了翻译、文字转语音和数据管理功能的中英翻译器。在这篇博客中&#xff0c;我将分享这个应用程序的主要特性和开发过程中的一些见…

CSC7261BH PD20瓦快充芯片

CSC7261BH是一款20瓦内置高压MOS的高性能、多工作模式的PWM控制芯片&#xff0c;内置多种保护机制。当系统为空载和轻载时&#xff0c;CSC7261BH 采用Burst和Green控制模式可有效地减少了空载和轻载时的损耗。当系统为中载和重载时&#xff0c;CSC7261BH采用CCM模式可有效提升电…

【kubernetes】K8S常见的发布方式

一、K8S常见的发布方式 蓝绿发布 两套环境交替升级&#xff0c;旧版本保留一定时间便于回滚 优点&#xff1a;对用户无感&#xff0c;是最安全的发布方式&#xff0c;业务稳定 缺点&#xff1a;需要两套系统&#xff0c;对资源要求比较高&#xff0c;成本特别高 灰度发布&…

STM32标准库学习笔记-3.外部中断

参考教程&#xff1a;【STM32入门教程-2023版 细致讲解 中文字幕】 中断 中断含义&#xff1a;在计算机执行主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&…

使用docker compose一键部署 Portainer

使用docker compose一键部署 Portainer Portainer 是一款轻量级的应用&#xff0c;它提供了图形化界面&#xff0c;用于方便地管理Docker环境&#xff0c;包括单机环境和集群环境。 1、创建安装目录 mkdir /data/partainer/ -p && cd /data/partainer2、创建docker…

【C语言篇】数组和函数的实践:扫雷游戏(附源码)

文章目录 前言扫雷游戏的分析和设计扫雷游戏的功能说明游戏的分析和设计文件结构设计 扫雷游戏的代码实现初始化棋盘打印棋盘布置雷排查雷 扫雷游戏的拓展 前言 源码在最后 扫雷游戏的分析和设计 经典扫雷游戏 扫雷游戏的功能说明 使⽤控制台实现经典的扫雷游戏 游戏可以通…

8月14日微语报,星期三,农历七月十一

8月14日微语报&#xff0c;星期三&#xff0c;农历七月十一&#xff0c;工作愉快&#xff0c;生活喜乐&#xff01; 一份微语报&#xff0c;众览天下事&#xff01; 1、巴黎奥运会&#xff1a;32项次世界纪录被刷新&#xff0c;125项次奥运纪录被改写。 2、国家邮政局&#…

鸿蒙应用程序框架基础

鸿蒙应用程序框架基础 应用程序包基础知识应用的多Module设计机制Module类型 Stage模型应用程序包结构开发态包结构编译包形态发布台包结构选择合适的包类型 应用程序包基础知识 应用的多Module设计机制 **支持模块化开发&#xff1a;**一个应用通常会包含多种功能&#xff0…

【stm32项目】多功能智能家居室内灯光控制系统设计与实现(完整工程资料源码)

多功能智能家居室内灯光控制系统设计与实现 目录&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、项目背景与目标 二、国内外研究现状&#xff1a; 2.1 国内研究现状&#xff1a; 2.2 国外研究现状&#xff1a; 2.3 发展趋势 三、硬件电路设计 3.1 总体概述 3.2 硬件连接总…

[NSSCTF 2022 Spring Recruit]babyphp

if嵌套&#xff0c;先过第一个if&#xff0c;需要a不含数字而且intval取整数 intval:通过使用指定的进制 base 转换&#xff08;默认是十进制&#xff09;&#xff0c;返回变量 value 的 int 数值。 intval() 不能用于 object&#xff0c;否则会产生 E_WARNING 错误并返回 1。…

微服务实战系列之玩转Docker(十)

前言 我们知道Docker的“使命”是为了快速完成应用的迁移和部署。为提升它的战斗能力&#xff0c;Docker官方携手发布了Docker Swarm—— 一个快速完成Docker集群构建的利器。那么请先回忆一下本系列第八篇&#xff08;重点compose&#xff09;和第九篇&#xff08;重点networ…

歌曲爬虫下载

本次编写一个程序要爬取歌曲音乐榜https://www.onenzb.com/ 里面歌曲。有帮到铁子的可以收藏和关注起来&#xff01;&#xff01;&#xff01;废话不多说直接上代码。 1 必要的包 import requests from lxml import html,etree from bs4 import BeautifulSoup import re impo…

Kaggle竞赛——心脏病患者预测与可视化

目录 准备工作1. 特征解释2. 生成探索性数据分析报告2.1 数据集导入2.2 生成数据分析报告 3. 可视化分析3.1 特征相关性分析3.2 患病人数统计3.3 特征与是否患病之间的关系 4. 数据处理4.1 定类数据处理4.2 独热编码 5. 模型搭建5.1 随机森林模型5.2 可视化决策树5.3 特征重要性…