当前位置: 首页 > news >正文

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

绪论​:
“节省时间的方法就是全力以赴的将所要做的事情完美快速的做完,不留返工重新学习的时间,才能省下时间给其他你认为重要的东西。”
本章主要讲到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 printstring 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;//将报头信息添加进去,还包括了两个\nreturn 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);//取出字符串lenint 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;//不指定iplocal.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字符串流中获取数据反序列化给到rootif(res){_result = root["result"].asInt();//asInt转化成整形_code = root["code"].asInt();}return res;
}

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

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

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


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

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

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

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

相关文章:

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

绪论​&#xff1a; “节省时间的方法就是全力以赴的将所要做的事情完美快速的做完&#xff0c;不留返工重新学习的时间&#xff0c;才能省下时间给其他你认为重要的东西。” 本章主要讲到OSI网络协议栈中的应用层的作用和再次在应用层的角度理解协议的具体意义&#xff0c;以及…...

【NXP-MCXA153】i2c驱动移植

介绍 ‌I2C总线由飞利浦公司开发&#xff0c;是一种串行单工通信总线&#xff0c;它主要用于连接微控制器和其他外围设备并在总线上的器件之间传送信息&#xff08;需要指定设备地址&#xff09;&#xff1b;常见的i2c设备有EEPROM、触摸屏、各种IoT传感器、时钟模块等&#x…...

C++(11)类语法分析(2)

C(10)之类语法分析(2) Author: Once Day Date: 2024年8月17日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客 …...

数字验证每日十问--(3)

深拷贝和浅拷贝的区别&#xff1f; 当只拷贝对象中的成员变量和声明的句柄时&#xff0c;称为浅拷贝。浅拷贝只把对象中的句柄复制了&#xff0c;却没有复制句柄b所指向的对象。这会导致复制后&#xff0c;a2中的句柄b 和 a1 中的句柄b指向同一个对象&#xff0c;如果a2中的句…...

22.给定 n 对括号,实现一个算法生成所有可能的正确匹配的括号组合

22. Generate Parentheses 题目 给定 n 对括号,编写一个函数生成所有可能的正确匹配的括号组合。 例如,当 n = 3 时,可能的组合集合为: ["((()))","(()())","(())()","()(())","()()()" ]题目大意 给出 n 代表生成…...

检测到目标URL存在http host头攻击漏洞

漏洞描述 修复措施 方法一&#xff1a; nginx 的 default_server 指令可以定义默认的 server 去处理一些没有匹配到 server_name 的请求&#xff0c;如果没有显式定义&#xff0c;则会选取第一个定义的 server 作为 default_server。 server { …...

C++奇迹之旅:手写vector模拟实现与你探索vector 容器的核心机制与使用技巧

文章目录 &#x1f4dd;基本框架&#x1f320; 构造和销毁&#x1f309;vector()&#x1f309;vector(const vector& v)&#x1f309;vector(size_t n, const T& value T())&#x1f309;赋值拷贝构造&#xff1a;vector<T>& operator(vector<T> v)&a…...

018、钩子函数 mounted和beforeDestroy、父组件向子组件传递参数 props 的使用

文章目录 1、mounted 和 beforeDestroy1.1、mounted1.2、beforeDestroy 2、父组件向子组件传递参数 props2.1、子组件定义2.2、父组件调用子组件并传参 3、完整例子3.1、父组件 Tags.vue3.2、子组件 TagsMenu.vue3.3、效果图 1、mounted 和 beforeDestroy 1.1、mounted mount…...

xlnt在Windows中的dll,lib生成

前言 花了半天时间想要把xlnt 集成到VS2022 Cmake项目中,以我目前掌握的能力,Cmake语法对于我来说难懂,对于只是使用过Cmake编译MySQL,或是其他lib,dll库的小白来说,不应该为了显示自己能力多么出众,强行去配置一些程序内容。 生活中没有绝对的事情,有舍有得. https://github…...

【网络】私有IP和公网IP的转换——NAT技术

目录 引言 NAT工作机制​编辑 NAT技术的优缺点 优点 缺点 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 引言 公网被子网掩码划分为层状结构&#xff0c;一个公网IP的机器又可以用很多私有IP搭建内网。在日常生活场景中用的都是私有IP&#xff0c;例如手机&#xff0c;…...

java 面试 PDF 资料整理

“尊贵的求知者&#xff0c;作者特此献上精心编纂的Java面试宝典PDF&#xff0c;这份资料凝聚了无数面试精华与实战经验&#xff0c;是通往Java技术殿堂的钥匙。若您渴望在Java编程的求职之路上稳健前行&#xff0c;只需轻轻一点&#xff0c;完成这象征支持与认可的一键三联&am…...

初步认识Linux系统

前言 Linux系统具有许多优点&#xff0c;不仅系统性能稳定&#xff0c;而且是开源软件。其核心防火墙组件性能高效、配置简单&#xff0c;保证了系统的安全。在很多企业网络中&#xff0c;为了追求速度和安全&#xff0c;Linux不仅仅是被网络运维人员当作服务器使用&#xff0c…...

JavaScript AI 编程助手

JavaScript AI 编程助手 引言 随着人工智能技术的飞速发展&#xff0c;编程领域也迎来了前所未有的变革。JavaScript&#xff0c;作为全球最流行的编程语言之一&#xff0c;其与AI的结合为开发者带来了巨大的便利和无限的可能性。本文将探讨JavaScript AI编程助手的定义、功能…...

达梦数据库的系统视图v$datafile

达梦数据库的系统视图v$datafile 达梦数据库的V$DATAFILE 是一个重要的系统视图&#xff0c;提供了有关数据库数据文件的信息。 V$DATAFILE 系统视图 V$DATAFILE 视图用于显示数据库中每一个数据文件的详细信息。通过查询这个视图&#xff0c;数据库管理员可以了解数据文件的…...

Triton/window安装: triton-2.0.0-cp310-cp310-win_amd64.whl文件

下面这个github仓&#xff1a; https://github.com/PrashantSaikia/Triton-for-Windows/tree/main 安装命令也很简单&#xff0c;下载到本地后运行: pip install triton-2.0.0-cp310-cp310-win_amd64.whl...

应急响应-DDOS-典型案例

某单位遭受DDoS攻击事件如下 事件背景 2019年2月17日&#xff0c;某机构门户网站无法访问&#xff0c;网络运维人员称疑似遭受DDoS攻击&#xff0c;请求应急响应工程师协助。 事件处置 应急响应工程师在达到现场后&#xff0c;通过查看流量设备&#xff0c;发现攻击者使用僵…...

JAVA学习之知识补充(下)

六&#xff1a;File类与IO流&#xff1a; 这里给出三种常见的初始化方法&#xff1a; 通过文件路径初始化: File file new File("C:/example/test.txt");这种方法用于创建一个文件对象&#xff0c;该文件对象表示指定路径的文件或目录。例如&#xff1a;File fil…...

qt生成一幅纯马赛克图像

由于项目需要&#xff0c;需生成一幅纯马赛克的图像作为背景&#xff0c;经过多次测试成功&#xff0c;记录下来。 方法一&#xff1a;未优化方法 1、代码&#xff1a; #include <QImage> #include <QDebug> #include <QElapsedTimer>QImage generateMosa…...

python循环——九九乘法表(更加轻松的理解循环结构)

感受 首先&#xff0c;得明确意识到这个问题&#xff0c;就是我的循环结构学的一塌糊涂&#xff0c;完全不能很好的使用这个循环来实现各种九九乘法表达输出&#xff0c;这样的循环结构太差了&#xff0c;还需要我自己找时间来补充一下循环的使用&#xff0c;来拓宽自己的思考方…...

UDS诊断系列之十八故障码的状态掩码

在谈19服务的子功能之前&#xff0c;先说一下故障码&#xff08;DTC&#xff09;的状态掩码是什么。 一、状态掩码 状态掩码由八个状态位构成&#xff0c;客户端利用它向服务器请求与其状态相匹配的DTC信息。当服务器接收到来自客户端的请求时&#xff0c;它会通过过滤匹配的…...

【jvm】直接引用

目录 1. 说明2. 形式3. 特点4. 生成过程5. 作用 1. 说明 1.在Java虚拟机&#xff08;JVM&#xff09;中&#xff0c;直接引用&#xff08;Direct Reference&#xff09;是相对于符号引用&#xff08;Symbolic Reference&#xff09;而言的&#xff0c;它是指向内存中实际存在的…...

PythonStudio 控件使用常用方式(二十七)TActionList

PythonStudio是一个极强的开发Python的IDE工具&#xff0c;官网地址是&#xff1a;https://glsite.com/ &#xff0c;在官网可以下载最新版的PythonStudio&#xff0c;同时&#xff0c;在使用PythonStudio时&#xff0c;它也能及时为用户升到最新版本。它使用的是Delphi的控件&…...

PDF 转Word 开源库

1. Apache PDFBox Apache PDFBox 是一个开源的 Java 库&#xff0c;用于创建和操作 PDF 文档。虽然 PDFBox 本身没有直接支持 PDF 转 Word 的功能&#xff0c;但它可以提取 PDF 内容&#xff0c;你可以结合其他方法将这些内容写入 Word。 添加依赖 <dependency><gr…...

Docker - 深入理解Dockerfile中的 RUN, CMD 和 ENTRYPOINT

RUN docker file 中的 RUN 命令相对来教容易理解 RUN 指令用于在构建镜像时执行命令&#xff0c;这些命令会在 Docker 镜像的构建过程中执行。常用于安装软件包、设置环境变量、创建目录等。RUN 指令会在镜像构建中创建新的镜像层&#xff0c;每个 RUN 指令都会创建一个新的镜…...

Python 函数式编程 内置高阶函数及周边【进阶篇 3】推荐

前面我们已经总结并实践了用python获取到了数据。也介绍了python中http网络请求的几种方式&#xff0c;正在学习python开发语言或者对python3知识点生疏需要回顾的请点这里 &#xff0c;本章主要总结了函数式编程及特点 和 python中内置的高阶函数及周边知识&#xff0c;方便自…...

【Rust光年纪】探秘Rust GUI库:从安装配置到API概览

Rust语言GUI库全方位比较&#xff1a;选择适合你的工具 前言 在现代软件开发中&#xff0c;图形用户界面&#xff08;GUI&#xff09;库扮演着至关重要的角色。随着Rust语言的不断发展&#xff0c;越来越多的优秀的GUI库也相继问世&#xff0c;为Rust开发者提供了更多选择。本…...

Element plus部分组件样式覆盖记录

文章目录 一、el-button 样式二、Popconfirm 气泡确认框三、Popover 气泡卡片四、Checkbox 多选框五、Pagination 分页六、Form 表单七、Table 表格 一、el-button 样式 html&#xff1a; <el-button class"com_btn_style">button</el-button>样式覆盖…...

重塑业务生态,Vatee万腾平台:引领行业变革的新引擎

在数字经济浪潮汹涌的今天&#xff0c;传统行业的边界正被不断模糊与重塑&#xff0c;新兴技术如云计算、大数据、人工智能等正以前所未有的速度改变着商业世界的面貌。在这一背景下&#xff0c;Vatee万腾平台应运而生&#xff0c;以其独特的创新模式和强大的技术实力&#xff…...

标准术语和定义中的【架构】应该如何描述

一、参考国家标准和国际标准中对“架构”的描述 &#xff08;1&#xff09;GB/T 8566-2022 国家标准 架构的术语描述&#xff1a;(系统)在其环境中的一些基本概念或性质,体现在其元素关系,以及设计与演进原则中。 &#xff08;2&#xff09;ISO/IEC/IEEE 42010 国际标准 架构的…...

华为鸿蒙Core Vision Kit 骨骼检测技术

鸿蒙Core Vision Kit 是华为鸿蒙系统中的一个图像处理框架&#xff0c;旨在提供各种计算机视觉功能&#xff0c;包括物体检测、人脸识别、文本识别等。骨骼检测是其中的一项功能&#xff0c;主要用于检测和识别人类身体的骨骼结构。 骨骼检测的关键点 骨骼点检测&#xff1a;通…...

Table API SQL系统(内置)函数System (Built-in) Function详解

目录 函数类型 引用函数 函数精确引用 函数模糊引用 函数解析顺序 精确的函数引用 模糊的函数引用 系统函数 标量函数(Scalar Functions) 比较函数(Comparison Functions) 逻辑函数(Logical Functions) 算术函数(Arithmetic Functions) 字符串函数(Strin…...

一键运行RocketMQ5.3和Dashboard

一键运行RocketMQ5.3和Dashboard 目录 一键运行RocketMQ5.3和Dashboard通过Docker Compose 来一键启动运行的容器包括docker-compose.yml文件运行命令启动本地效果查看 参考信息 通过Docker Compose 来一键启动 运行的容器包括 NameServerBrokerProxyDashBoard docker-compo…...

HAL STM32 SG90舵机驱动控制

HAL STM32 SG90舵机驱动控制 &#x1f516;测试对象&#xff1a;STM32F103SG90舵机 &#x1f33c;功能实现&#xff1a;通过串口指令&#xff0c;控制SG90舵机转动到指定角度。 ✨在实际硬件舵机驱动过程中&#xff0c;使用SG90普通舵机空载运转情况下&#xff0c;电流在180mA…...

【Kubernetes】k8s集群图形化管理工具之rancher

目录 一.Rancher概述 1.Rancher简介 2.Rancher与k8s的关系及区别 3.Rancher具有的优势 二.Rancher的安装部署 1.实验准备 2.安装 rancher 3.rancher的浏览器使用 一.Rancher概述 1.Rancher简介 Rancher 是一个开源的企业级多集群 Kubernetes 管理平台&#xff0c;实…...

AI编程系列一1小时完成链家房价爬虫程序

背景 AI编程实在太火&#xff0c;写了很多年的Java&#xff0c;现在Python 和Go 简单好用&#xff0c;今天结合智谱清言快速完成一个程序爬虫程序&#xff0c;没有任何Python 编程经验&#xff0c;只需要会提问&#xff0c;熟悉简单HTML结构即可。未来一定是有业务能力者的福…...

【JavaEE初阶】文件内容的读写—数据流

目录 &#x1f4d5; 引言 &#x1f334; 数据流的概念 &#x1f6a9; 数据流分类 &#x1f333; 字节流的读写 &#x1f6a9; InputStream&#xff08;从文件中读取字节内容) &#x1f6a9; OutputStream&#xff08;向文件中写内容&#xff09; &#x1f384; 字符流的…...

Spring Boot项目中使用Sharding-JDBC实现读写分离

Sharding-JDBC是一个分布式数据库中间件&#xff0c;它不仅支持数据分片&#xff0c;还可以轻松实现数据库的读写分离。下面是如何在Spring Boot项目中集成Sharding-JDBC并实现读写分离的详细步骤&#xff1a; 目录 1. 引入依赖 2. 配置数据源 3. 配置Sharding-JDBC相关参数…...

【网络安全】SSO登录过程实现账户接管

未经许可,不得转载。 文章目录 正文正文 登录页面展示了“使用 SSO 登录”功能: 经分析,单点登录(SSO)系统的身份验证过程如下: 1、启动SSO流程:当用户点击按钮时,浏览器会发送一个GET请求到指定的URL: /idp/auth/mid-oidc?req=[UNIQUE_ID]&redirect_uri=[REDI…...

Admin.NET源码学习(3:LazyCaptcha使用浅析)

Admin.NET项目前端登录页面的验证码图片默认使用动态图&#xff0c;且图形内容为阿拉伯数字运算&#xff08;如下图所示&#xff09;&#xff0c;用户输入正确的计算结果才能正常登录。项目采用LazyCaptcha模块生成验证码及动态图。   在Admin.NET.Core项目中添加了Lazy.Cap…...

在原生未启用kdump的BCLinux 8系列服务器上启用kdump及报错处理

本文记录了在原生未启用kdump的BCLinux 8系列操作系统的服务器上手动启用kdump服务及报错处理的过程。 一、问题描述 BCLinux 8系列操作系统&#xff0c;系统初始化安装时未启用kdump服务&#xff0c;手动启动时报以下“No memory reserved for crash kernel”或“ConditionK…...

Android架构组件中的MVVM

Android架构组件中的MVVM&#xff08;Model-View-ViewModel&#xff09;模式是一种广泛应用的设计模式&#xff0c;它通过将应用程序分为三个主要部分&#xff08;Model、View、ViewModel&#xff09;来分离用户界面和业务逻辑&#xff0c;从而提高代码的可维护性、可扩展性和可…...

走向绿色:能源新选择,未来更美好

当前&#xff0c;全球范围内可再生能源正经历着从辅助能源向核心能源的深刻转型&#xff0c;绿色能源日益渗透至居住、出行、日常应用等多个领域&#xff0c;深刻影响着我们的生活方式&#xff0c;使我们能够更加充分地体验清洁能源所带来的优质生活。 一、绿色能源与“住” …...

鸿蒙装饰器的介绍

State装饰器&#xff0c; State装饰的变量&#xff0c;称为状态变量&#xff0c;与声明式范式中的其他被装饰变量一样&#xff0c;是私有的&#xff0c;只能从组件内部访问&#xff0c;在声明时&#xff0c;必须指定其类型和本地初始化。 Provide装饰器和Consume装饰器&#…...

零基础5分钟上手亚马逊云科技核心云架构知识 - 权限管理最佳实践

简介&#xff1a; 欢迎来到小李哥全新亚马逊云科技AWS云计算知识学习系列&#xff0c;适用于任何无云计算或者亚马逊云科技技术背景的开发者&#xff0c;通过这篇文章大家零基础5分钟就能完全学会亚马逊云科技一个经典的服务开发架构方案。 我会每天介绍一个基于亚马逊云科技…...

[数据库][知识]SQL Server、MySQL 和 Oracle 的默认端口和数据库链接

SQL Server、MySQL 和 Oracle 的默认端口号、连接 URL 和驱动类名。以下是对每个数据库连接信息的简要说明&#xff1a; SQL Server 默认端口号&#xff1a;1433JDBC URL 格式&#xff1a;jdbc:sqlserver://localhost:1433;DatabaseNamedbnameJDBC 驱动类名&#xff1a;com.mic…...

【Unity教程】使用 Animation Rigging实现IK制作程序化的动画

在 Unity 开发中&#xff0c;为角色创建逼真且自适应的动画是提升游戏体验的关键。在本教程中&#xff0c;我们将结合 Animation Rigging 工具和 IK&#xff08;Inverse Kinematics&#xff0c;反向运动学&#xff09;插件来实现程序化的动画。 视频教程可以参考b战大佬的视频 …...

OBS混音器(Mixers)的重要性和配置指南

在进行直播或录制时,音频管理是非常关键的一环,特别是在需要同时处理多个音频源的复杂设置中。OBS Studio提供了强大的音频管理工具,其中“混音器”功能扮演了核心角色。混音器(Mixers)在OBS中用于控制不同音频源的输出路由,允许用户精确控制哪些音源出现在最终的直播或录…...

Ubuntu安装Anaconda3

本文详细阐述了在 Ubuntu 系统中安装 Anaconda3 的完整流程。包括 Anaconda3 安装包的获取途径&#xff0c;具体安装过程中的每一个步骤及注意事项&#xff0c;还有安装后的环境变量设置和安装成功的验证方法。旨在为 Ubuntu 用户提供清晰、易懂且准确的 Anaconda3 安装指南&am…...

数据类型解码:INT、VARCHAR、DATETIME的深度解析与实践

标题&#xff1a;数据类型解码&#xff1a;INT、VARCHAR、DATETIME的深度解析与实践 在软件开发和数据库设计中&#xff0c;数据类型是构建数据模型的基础。准确理解和使用数据类型&#xff0c;如INT、VARCHAR、DATETIME&#xff0c;对于确保数据的完整性、性能和安全性至关重…...

基于单片机的智能晾衣系统设计

摘 要 &#xff1a;在网络信息技术的推动下&#xff0c;智能家居得到了广泛应用&#xff0c;文章根据当前的市场动态&#xff0c;针对基于单片机的智能晾衣系统设计展开论述&#xff0c;具体包括两个方面的内容———硬件设计和软件设计。 关键词 &#xff1a;单片机&#xff…...