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

1.应用层
1.1再谈“协议”
之前所写的协议,本质就是一个约定,而现在再把这个约定实体化:
现在通过网络版计数器,来更好的理解:
为了网络上更便利的发送,要进行序列化和反序列化操作
- 序列化:将协议对应的结构化数据,转换成“字符串”字节流(目的:方便网络发送)
也就是将多条信息打包成一条信息(一个字符串) - 反序列化:将“字符串”字节流,转换成结构化数据(为上层业务随时提前有效字段)
也就是再把这条信息再分回多条

协议就是发送的信息的结构(上方发送来的信息的原本结构就是那三条信息)
对于这些信息就可以把协议描述成一个结构体,并且双方对这个协议结构体都提前约定好的
客户端和服务端用同一个结构体(数据类型)对特定的字段进行解释,那这就是计算机层面上的协议(计算机间的约定)

而在网络中不直接传递整个协议结构:而是通过序列化(直接把结构体地址传过去:也就是char*)后在网络中传送,
到对方后再进行反序列化操作解析(通过强转为协议类型后选择对应的数据填充协议结构:(sturct Message*)p->name …)。
这样双方就能进行序列化和反序列的操作(也就是协议的目的)
应用层在不同的系统,平台上会有很多种协议结构,这样就不能保证所有平台双方统一正确的读取(平台差异),所以经过序列化和反序列化就能很好的解决这个问题(提前约定了协议就能获取/发送所要的数据)。
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应答
在下面模拟实现中,把序列化、反序列化都分成了两份:
- 处理 真正的数据(x op y)
- 添加报头(len\n真正的数据 \n)
也就是对应函数:
- 处理真正数据的序列化:Serialize、反序列化:DeSerialize
- 添加报头信息: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成熟的序列化和反序列化方案:
常见的序列化协议
- json:允许采用 {"key ", “value”} 的方式将信息组织起来
- protobuf
- 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)
本章完。预知后事如何,暂听下回分解。
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量计算机网络细致内容,早关注不迷路。
相关文章:
网络协议栈应用层的意义(内含思维导图和解析图通俗易懂超易理解)
绪论: “节省时间的方法就是全力以赴的将所要做的事情完美快速的做完,不留返工重新学习的时间,才能省下时间给其他你认为重要的东西。” 本章主要讲到OSI网络协议栈中的应用层的作用和再次在应用层的角度理解协议的具体意义,以及…...
【NXP-MCXA153】i2c驱动移植
介绍 I2C总线由飞利浦公司开发,是一种串行单工通信总线,它主要用于连接微控制器和其他外围设备并在总线上的器件之间传送信息(需要指定设备地址);常见的i2c设备有EEPROM、触摸屏、各种IoT传感器、时钟模块等&#x…...
C++(11)类语法分析(2)
C(10)之类语法分析(2) Author: Once Day Date: 2024年8月17日 一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦… 漫漫长路,有人对你微笑过嘛… 全系列文章可参考专栏: 源码分析_Once-Day的博客-CSDN博客 …...
数字验证每日十问--(3)
深拷贝和浅拷贝的区别? 当只拷贝对象中的成员变量和声明的句柄时,称为浅拷贝。浅拷贝只把对象中的句柄复制了,却没有复制句柄b所指向的对象。这会导致复制后,a2中的句柄b 和 a1 中的句柄b指向同一个对象,如果a2中的句…...
22.给定 n 对括号,实现一个算法生成所有可能的正确匹配的括号组合
22. Generate Parentheses 题目 给定 n 对括号,编写一个函数生成所有可能的正确匹配的括号组合。 例如,当 n = 3 时,可能的组合集合为: ["((()))","(()())","(())()","()(())","()()()" ]题目大意 给出 n 代表生成…...
检测到目标URL存在http host头攻击漏洞
漏洞描述 修复措施 方法一: nginx 的 default_server 指令可以定义默认的 server 去处理一些没有匹配到 server_name 的请求,如果没有显式定义,则会选取第一个定义的 server 作为 default_server。 server { …...
C++奇迹之旅:手写vector模拟实现与你探索vector 容器的核心机制与使用技巧
文章目录 📝基本框架🌠 构造和销毁🌉vector()🌉vector(const vector& v)🌉vector(size_t n, const T& value T())🌉赋值拷贝构造: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技术的优缺点 优点 缺点 个人主页:东洛的克莱斯韦克-CSDN博客 引言 公网被子网掩码划分为层状结构,一个公网IP的机器又可以用很多私有IP搭建内网。在日常生活场景中用的都是私有IP,例如手机,…...
java 面试 PDF 资料整理
“尊贵的求知者,作者特此献上精心编纂的Java面试宝典PDF,这份资料凝聚了无数面试精华与实战经验,是通往Java技术殿堂的钥匙。若您渴望在Java编程的求职之路上稳健前行,只需轻轻一点,完成这象征支持与认可的一键三联&am…...
初步认识Linux系统
前言 Linux系统具有许多优点,不仅系统性能稳定,而且是开源软件。其核心防火墙组件性能高效、配置简单,保证了系统的安全。在很多企业网络中,为了追求速度和安全,Linux不仅仅是被网络运维人员当作服务器使用,…...
JavaScript AI 编程助手
JavaScript AI 编程助手 引言 随着人工智能技术的飞速发展,编程领域也迎来了前所未有的变革。JavaScript,作为全球最流行的编程语言之一,其与AI的结合为开发者带来了巨大的便利和无限的可能性。本文将探讨JavaScript AI编程助手的定义、功能…...
达梦数据库的系统视图v$datafile
达梦数据库的系统视图v$datafile 达梦数据库的V$DATAFILE 是一个重要的系统视图,提供了有关数据库数据文件的信息。 V$DATAFILE 系统视图 V$DATAFILE 视图用于显示数据库中每一个数据文件的详细信息。通过查询这个视图,数据库管理员可以了解数据文件的…...
Triton/window安装: triton-2.0.0-cp310-cp310-win_amd64.whl文件
下面这个github仓: https://github.com/PrashantSaikia/Triton-for-Windows/tree/main 安装命令也很简单,下载到本地后运行: pip install triton-2.0.0-cp310-cp310-win_amd64.whl...
应急响应-DDOS-典型案例
某单位遭受DDoS攻击事件如下 事件背景 2019年2月17日,某机构门户网站无法访问,网络运维人员称疑似遭受DDoS攻击,请求应急响应工程师协助。 事件处置 应急响应工程师在达到现场后,通过查看流量设备,发现攻击者使用僵…...
JAVA学习之知识补充(下)
六:File类与IO流: 这里给出三种常见的初始化方法: 通过文件路径初始化: File file new File("C:/example/test.txt");这种方法用于创建一个文件对象,该文件对象表示指定路径的文件或目录。例如:File fil…...
qt生成一幅纯马赛克图像
由于项目需要,需生成一幅纯马赛克的图像作为背景,经过多次测试成功,记录下来。 方法一:未优化方法 1、代码: #include <QImage> #include <QDebug> #include <QElapsedTimer>QImage generateMosa…...
python循环——九九乘法表(更加轻松的理解循环结构)
感受 首先,得明确意识到这个问题,就是我的循环结构学的一塌糊涂,完全不能很好的使用这个循环来实现各种九九乘法表达输出,这样的循环结构太差了,还需要我自己找时间来补充一下循环的使用,来拓宽自己的思考方…...
UDS诊断系列之十八故障码的状态掩码
在谈19服务的子功能之前,先说一下故障码(DTC)的状态掩码是什么。 一、状态掩码 状态掩码由八个状态位构成,客户端利用它向服务器请求与其状态相匹配的DTC信息。当服务器接收到来自客户端的请求时,它会通过过滤匹配的…...
【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
MySQL 8.0 OCP 英文题库解析(十三)
Oracle 为庆祝 MySQL 30 周年,截止到 2025.07.31 之前。所有人均可以免费考取原价245美元的MySQL OCP 认证。 从今天开始,将英文题库免费公布出来,并进行解析,帮助大家在一个月之内轻松通过OCP认证。 本期公布试题111~120 试题1…...
Pinocchio 库详解及其在足式机器人上的应用
Pinocchio 库详解及其在足式机器人上的应用 Pinocchio (Pinocchio is not only a nose) 是一个开源的 C 库,专门用于快速计算机器人模型的正向运动学、逆向运动学、雅可比矩阵、动力学和动力学导数。它主要关注效率和准确性,并提供了一个通用的框架&…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...
windows系统MySQL安装文档
概览:本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容,为学习者提供全面的操作指导。关键要点包括: 解压 :下载完成后解压压缩包,得到MySQL 8.…...

