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

Linux | 网络通信 | 序列化和反序列化的讲解与实现

文章目录

    • 为什么要序列化?
    • 协议的实现
    • 服务端与客户端代码实现

为什么要序列化?

由于默认对齐数的不同,不同的平台对相同数据进行内存对齐后,可能得到不同的数据。如果直接将这些数据进行网络传输,对方很可能无法正确的获取这些数据,通信也就失去了意义,为保证通信数据的准确性,我们需要设计序列化方式,将数据序列化成字节流,再进行传输,接收方通过对应的反序列化方式对字节流进行解析,从而得到原始且正确的数据。总而言之,序列化与反序列化是为了使通信正确的进行而对数据进行的处理操作

序列化与反序列化只是一种指导思想,关于它的具体实现有很多种,如XML,JSON等等。为了更好理解序列化与反序列化的过程,我在这里自己定制一个协议(这个协议只是模拟实现),该协议是针对简单计算进行设计的

协议的实现

在进行网络传输之前,我们要将数据转换成字符串的形式,以字节流的方式发送信息,这里就涉及到有效载荷和报头的相关知识,报头在有效载荷的前面,也就是字符串的最开始,报头和有效载荷之间用分隔符分割,其表明了有效载荷的长度,比如报头表示有效载荷的长度为n字节,那么分隔符向后n个字节都表示有效载荷。但是报头的长度谁来表示?读取字节流时,一直读取直到遇到分隔符,分隔符之前的数据就是报头。但这里有一个问题?为什么需要使用报头表示有效载荷的长度?有效载荷之间用分隔符分隔不就行了吗?注意一个问题:假如分隔符恰好是有效载荷的一部分数据,直接用分隔符分隔有效载荷的做法不够严谨,需要使用报头来表示有效载荷的长度,并且表示长度的报头一般不会包含分隔符(或者说其中的数据不会和分隔符冲突)

客户端需要和服务端进行通信,通信肯定要发送数据吧,但是客户端上的数据大多是结构化的数据,由于内存对其的问题,不能直接向服务端发送数据,因此,我们需要将结构化的数据转换成特定格式的字符串格式,这个过程分为两步:一是将结构化数据转换成字符串形式的有效载荷,二是为有效载荷添加报头。如此就得到了报头+有效载荷,此时才能将其发送给服务端

我们将:为有效载荷添加/删除报头的操作称为encode/decode,生成有效载荷与解析有效载荷的操作称为serialize/deserialize,也可以叫做序列化和反序列化。综上,客户端发送数据前需要serialize+encode,服务端接收数据后需要decode+deserialize。关于协议的定制,就是这4个接口的具体实现

先说明encode,我们将需要进行encode的字符串str作为参数,然后创建一个新的字符串ret,在ret后追加str的长度(当然了,需要将整数形式的长度转换成字符串形式),接着追加分隔符,最后追加str和分隔符,返回ret

#define CRLF "\n\r"
// 根据参数str,返回编码得到的ret
string encode(const string& str)
{string ret = to_string(str.size());ret += CRLF;ret += str;ret += CRLF;return ret;
}

接着是decode,客户端可能发送一次完成的请求,也可能发送一次不完整的请求,还可能发送多次请求,但我们只需要区分客户发送不完整请求的情况。我们将需要decode的字符串str作为函数参数,再将一个uint32_t类型的数据len以引用的方式作为第二个参数,如果有效载荷不完整,len的值为0,这样请求是否完整就有了判断依据。

接着调用str的find方法,查找分隔符,find将返回分隔符在str中第一次出现的下标位置,如果分隔符没有在str中出现,find返回的数值等于string::npos。所以这里进行判断,如果find返回npos,decode返回空串,用户发送的请求不完整,需要再次进行发送。找到分隔符在str中第一次出现的下标后,我们就可以取出报头,接着解析报头,拿到有效载荷的长度,判断str剩下空间是否足以存储有效载荷,如果剩余空间不足以存储这个有效载荷,那么decode返回空串,用户发送的请求不完整。如果剩余空间足以存储有效载荷,那么我们要将有效载荷返回,修改len为有效载荷的程度,并且删除str中第一个:报头+有效载荷数据。

// 根据参数str,对其解码,并检测str是否有完整的有效载荷,函数返回解码得到的字符串
string decode(string& str, uint32_t& payload_len)
{payload_len = 0;// 查找报头size_t head_pos = str.find(CRLF);if (head_pos == string::npos)return "";// 获取有效载荷的长度string head = str.substr(0, head_pos);int tmp_len = atoi(head.c_str());// 判断有效载荷是否完整if (str.size() - head_pos - 2 * strlen(CRLF) < tmp_len) // 有效载荷不完整return "";// 有效载荷完整,删除报头数据与有效载荷// 这是才修改payload_len参数payload_len = tmp_len; string package = str.substr(head_pos + strlen(CRLF), payload_len); // 删除之前保存有效载荷,因为要返回str.erase(0, payload_len + head_pos + 2 * strlen(CRLF));// 返回有效载荷的字符串return package;
}

关于substr的边界确定,这里就不展开赘述,自己把握就行了。说明一下,服务端每次接收客户端的请求,都是将这些请求追加到一个string后面,所以当我们获取了一次完整的报头+有效载荷,我们就要将其删除,没有获取到完整的报头+有效载荷是,就将下次用户的请求追加到string后面,再次判断是否有完整的请求

encode和decode就讲解完了,接下来是serialize和deserialize。由于客户端发送的数据和服务端发送的数据不同,所以序列化和反序列化要设计两套接口。我们将客户端要发送的数据封装为Request,服务端要响应的数据封装为Response,这两个类有不同的serialize和deserialize方法。由于我是针对简单计算定制的序列化协议,所以客户端的Request就只有两个操作数,一个操作符,就比如1+1,然后服务端对于请求的响应Response,需要保存简单计算的结果与退出码(如果计算遇到错误就会设置退出码)

class Request
{
public:int _x;int _y;char _op;
};class Response
{
public:  // 退出码,0表示正常退出int _exit_code = 0;// 计算结果int _result;
};

关于Request的序列化接口serialize,我们用引用的方式接收用户传入的字符串str,这里默认str是空串,然后将_x,_op,_y对象依次转换成字符串(调用to_string接口),追加到str后,并在两个对象之间插入空格(追加一次对象再追加一次空格),这样就完成了Request的序列化。对于Request的反序列化,deserialize的参数str接收需要反序列化的字符串,使用str的find接口查找空格(因为计算表达式中,不会出现空格,所以我们将空格作为每个对象之间的分隔符),利用空格找到每个对象的位置,调用substr与atoi接口将它们从字符串转换成整形(操作符也是一个整数,这个可以看ASCII码表),为Request对象赋上相应的值。

// 用来发送的计算请求
class Request
{
public:void serialize(string& str){str += to_string(_x);str += SPACE;str+= to_string(_op);str += SPACE;str += to_string(_y);}bool deserialize(string& str){// 获取字符串中的两个空格,以及判断其正确性size_t spaceone_pos = str.find(SPACE);if (spaceone_pos == string::npos)return false;size_t spacetwo_pos = str.rfind(SPACE);if (spacetwo_pos == string::npos)return false;// 根据空格获取操作数与操作符string op = str.substr(spaceone_pos + strlen(SPACE), spacetwo_pos - spaceone_pos - strlen(SPACE));string x = str.substr(0, spaceone_pos);string y = str.substr(spacetwo_pos + strlen(SPACE));// 将得到的字符转换成类成员_x = atoi(x.c_str());_y = atoi(y.c_str());_op = atoi(op.c_str());return true;}int _x;int _y;char _op;
};

Response的序列化serialize也是如此,函数以引用的方式接收一个空串,在空串后追加exitcode和result,每个对象用空格分隔。反序列化deserialize也是接收需要反序列化的字符串str,用find查找空格的位置,根据空格获取其他操作数的位置,也是用substr和atoi接口

class Response
{
public:void serialisze(string& str){str += to_string(_exit_code);str += SPACE;str += to_string(_result);}bool deserialize(string& str){size_t space_pos = str.find(SPACE);if (space_pos == string::npos)return false;_exit_code = atoi(str.substr(0, space_pos).c_str());_result = atoi(str.substr(space_pos + strlen(SPACE)).c_str());return true;}// 退出码,0表示正常退出int _exit_code = 0;// 计算结果int _result = 0;
};

然后就是将用户输入的字符串转换成Request对象的函数,由于用户可能输入"1+1",“1 +1”,“1+ 1”,所以这里需要对这些空格进行特殊处理。首先使用strtok将两个操作数分开,对于左操作数,假设它现在是"1 “,我们从最后开始遍历,遍历到数字停下,在数字后插入’\0’就消除了这些多于空格。对于右操作数,它可能是” 1",我们就从头开始遍历,将指向右操作数的指针不断++,直到该指针指向的数据不再是空格。最后就是调用atoi得到整数形式的操作数

// 将message转换成Request
bool makeRequest(Request& req, char* message)
{char copy[1024] = {0};strcpy(copy, message);// 分割左右操作数char* left = strtok(copy, "+-*/%");char* right = strtok(nullptr, "+-*/%");if (!left || !right)return false;int left_len = strlen(left);int right_len = strlen(right);req._op = message[left_len];// 消除多余空格 int i = 0;for (i = left_len - 1; i >= 0; --i){if (left[i] != ' ')break;}left[i + 1] = '\0';for (i = 0; i < right_len; ++i){if (right[i] != ' ')break;elseright++;}// 将字符串形式的操作数转换成整数req._x = atoi(left);req._y = atoi(right);return true;
}

对于序列化和反序列化除了自己定义方法,也可以使用Json协议,使用别人定制好的成熟的方法,关于Json第三方库的安装,可以使用命令

sudo yum install -y jsoncpp-deve

至于要使用自己的方法还是Json协议,这里可以使用条件编译的方式,将两者方法都写进代码中

// 用来发送的计算请求
class Request
{
public:// 110 + 120void serialize(string& str){#ifdef MYSELFstr += to_string(_x);str += SPACE;str += to_string(_op);str += SPACE;str += to_string(_y);#else// 万能Json对象Json::Value root;root["x"] = _x;root["y"] = _y;root["op"] = _op;// 定义写对象Json::FastWriter fw;// 将value序列化,保存结果到str中str = fw.write(root);#endif}bool deserialize(string& str){#ifdef MYSELF// 获取字符串中的两个空格,以及判断其正确性size_t spaceone_pos = str.find(SPACE);if (spaceone_pos == string::npos)return false;size_t spacetwo_pos = str.rfind(SPACE);if (spacetwo_pos == string::npos)return false;// 根据空格获取操作数与操作符string op = str.substr(spaceone_pos + strlen(SPACE), spacetwo_pos - spaceone_pos - strlen(SPACE));string x = str.substr(0, spaceone_pos);string y = str.substr(spacetwo_pos + strlen(SPACE));// 将得到的字符转换成类成员_x = atoi(x.c_str());_y = atoi(y.c_str());_op = atoi(op.c_str());return true;#elseJson::Value root;Json::Reader rd;rd.parse(str, root);_x = root["x"].asInt();_y = root["y"].asInt();_op = root["op"].asInt();return true;#endif}int _x;int _y;char _op;
};// 对请求做出的响应
class Response
{
public:void serialisze(string& str){#ifdef MYSELFstr += to_string(_exit_code);str += SPACE;str += to_string(_result);#elseJson::Value root;root["exitcode"] = _exit_code;root["result"] = _result;Json::FastWriter fw;str = fw.write(root);#endif}bool deserialize(string& str){#ifdef MYSELFsize_t space_pos = str.find(SPACE);if (space_pos == string::npos)return false;_exit_code = atoi(str.substr(0, space_pos).c_str());_result = atoi(str.substr(space_pos + strlen(SPACE)).c_str());return true;#elseJson::Value root;Json::Reader rd;rd.parse(str, root);_exit_code = root["exitcode"].asInt();_result = root["result"].asInt();return true;#endif}// 退出码,0表示正常退出int _exit_code = 0;// 计算结果int _result = 0;
};

关于Json接口的使用:Json序列化,形成的字符串是kv格式的,比如说1+1序列化后就是

{“op”:43,“x”:1,“y”:1}

数据前面的字符串就是为反序列化建立的索引,先说序列化:Value是Json中的一个万能对象,用它可以序列化不同格式的数据,创建Value对象root,root[“x”, _x]就是在root中插入了一个键值对,把所有的数插入到root后,创建FastWrite对象,以root为参数调用其write方法,用string类型对象str接收write的返回值,str中保存的就是{“op”:43,“x”:1,“y”:1}这样的字符串,是将数据序列化后的结果。

至于Json的反序列化:我们创建Reader类型对象rd与Value类型对象root,调用rd的parse方法,将root和序列化后的字符串str作为parse的参数,parse方法会将反序列化后的结果写入root,此时我们就能根据当时创建root对象时,为数据建立的索引来还原数据,比如root[“x”].asInt(),将root中以"x"为key的value值以int的格式返回,这样我们就能将数据还原到我们的结构体中

关于条件编译,我们可以在makefile文件中,以命令行的方式,在编译源文件时创建宏,具体是-D 宏的名字
在这里插入图片描述

服务端与客户端代码实现

对于具体的服务端与客户端通信细节,可以看我的这篇文章,这里不再赘述,只说明大概的思路。

客户端与服务端建立链接后,客户端从键盘读取用户的输入,由于输入的是字符串,并且格式可能不规范,所以客户端需要对用户输入的字符串进行处理,将其转换成Request对象req,接着调用req的serialize方法,将计算表达式序列化,然后是encode,为有效载荷添加报头,最后将这样的数据发送给服务端。注意,不是发送完就结束的了,用户需要得到服务端的响应,得到一个计算结果,所以客户端需要调用read方法,读取服务端的响应,所以这里需要将得到的响应decode,得到有效载荷,接着deserialize,反序列化,将数据填充到Response对象res中,至此,一次客户端与服务端的通信完成。

对于服务端,与客户端连接后,需要接收来自客户端的请求,得到请求后,decode+deserialize,得到一个Request对象req,服务端是要提供服务的,所以这里需要服务端调用一个计算函数,将req的计算表达式计算出一个具体值,并且将结果和退出码保存到Response对象res中。然后对res序列化+encode,将这样的字符串返回给客户端,一次通信中服务端的工作才算完成

#include "util.hpp"
#include "protocol.hpp"
void usage(const char *filename)
{std::cout << "usage:\n\t"<< filename << "IP port" << std::endl;
}int main(int argc, char* argv[])
{if (argc != 3){usage(argv[0]);exit(USAG_ERRO);}std::string server_ip = argv[1];uint16_t server_port = atoi(argv[2]);// 服务器套接字的填充struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);server.sin_addr.s_addr = inet_addr(server_ip.c_str());// 套接字的创建int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){std::cerr << "socket: fail" << std::endl;exit(SOCK_FAIL);}// 与服务器的连接if (connect(sockfd, (const struct sockaddr*)&server, sizeof(server)) < 0){std::cerr << "connect: fail" << std::endl;exit(CONN_FAIL);}std::cout << "connect done" << std::endl;while (true){std::cout << "请输入计算表达式#";char message[1024] = {0};std::cin.getline(message, sizeof(message));if (strcasecmp("quit", message) == 0){// 注意不要退出,让客户端向服务器发送quit,服务器接收quit将关闭服务ssize_t w_ret = write(sockfd, message, sizeof(message));break;}// 创建请求对象Request req;// 用用户的输入填充req对象makeRequest(req, message);// 序列化,得到可以发送的字符流string package = "";req.serialize(package);// 对字节流编码package = encode(package);ssize_t w_ret = write(sockfd, package.c_str(), package.size());// 发送package失败if (w_ret <= 0){std::cerr << "write: fail" << std::endl;break;}// 发送package成功,接收服务端的响应char re_tmp[1024] = {0};ssize_t r_ret = read(sockfd, re_tmp, sizeof(re_tmp));// 接收失败if (r_ret <= 0){std::cerr << "read: fail" << std::endl;break;}// 接收成功re_tmp[r_ret] = '\0';string re_package = re_tmp;// 解码packageuint32_t payload_len = 0;re_package = decode(re_package, payload_len);// 创建Response对象 Response res;// 将接收的有效载荷反序列化到res中res.deserialize(re_package);// for testcout << "result:" << res._result << ", exit_code:" << res._exit_code << endl;}return 0;
}
#include "util.hpp"
#include "task.hpp"
#include "threadpool.hpp"
#include <signal.h>
#include <sys/wait.h>
#include "protocol.hpp"Response calculate(const Request& req)
{Response res;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 (req._y == 0)res._exit_code = -1; // -1表示除0错误elseres._result = req._x / req._y;break;case '%':if (req._y == 0)res._exit_code = -2; // -2表示模0错误elseres._result = req._x % req._y;break;default:res._exit_code = -3; // -3表示操作符错误break;}return res;
}void netCal(int sockfd)
{while (true){// 获取客户端的请求char tmp_buf[1024] = {0};// total_packag用来保存客户端的输入,就算输入信息不完整string total_package = "";// 读取客户端输入的信息ssize_t r_ret = read(sockfd, tmp_buf, sizeof(tmp_buf));   // 读取失败if (r_ret < 0){cout << "read fail" << endl;break;}// 客户端退出else if (r_ret == 0){cout << "client quit" << endl;break;}// 读取成功 else{tmp_buf[r_ret] = '\0';total_package += tmp_buf;Response res;Request req;uint32_t payload_len = 0;// 解码客户发送的信息string cli_package = decode(total_package, payload_len);// 没有获取完整的有效载荷if (payload_len == 0) continue;// 反序列化有效载荷,得到数据req.deserialize(cli_package);// for testcout << "x:" << req._x << ' ' << "op:" << req._op << ' ' << "y:" << req._y << endl;// 得到响应请求的对象res = calculate(req);// 序列化响应对象string package = "";res.serialisze(package);// 对响应进行编码package = encode(package);// 向客户端发送响应write(sockfd, package.c_str(), package.size());// for testcout << "完成一次计算" << endl;}}
}class tcpServer
{
public:tcpServer(uint16_t port, std::string ip = "") : _ip(ip), _port(port) {}~tcpServer() {}void init(){// 创建套接字文件_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listen_sockfd < 0){std::cerr << "socket: fail" << std::endl;exit(SOCK_FAIL);}// 填充套接字信息struct sockaddr_in local;local.sin_family = AF_INET;local.sin_port = htons(_port);_ip.empty() ? local.sin_addr.s_addr = INADDR_ANY : inet_aton(_ip.c_str(), &local.sin_addr);// 将信息绑定到套接字文件中if (bind(_listen_sockfd, (const struct sockaddr *)&local, sizeof(local)) < 0){std::cerr << "bind: fail" << std::endl;exit(BIND_FAIL);}// 至此,套接字创建完成,所有的步骤与udp通信一样// 使套接字进入监听状态if (listen(_listen_sockfd, 5) < 0){std::cerr << "listen: fail" << std::endl;exit(LSTE_FAIL);}// 套接字初始化完成std::cout << "listen done" << std::endl;// 获取线程池的单例_tp = threadpool<Task>::get_instance();std::cout << "threadpool ready" << endl;}void loop(){// 先启动线程池_tp->start();// signal(SIGCHLD, SIG_IGN); // 设置SIGCHLD信号为忽略,这样子进程就会自动释放资源// 创建保存套接字信息的结构体struct sockaddr_in peer;socklen_t peer_len = sizeof(peer);// 接受监听队列中的套接字请求while (1){int server_sockfd = accept(_listen_sockfd, (struct sockaddr *)&peer, &peer_len);if (server_sockfd < 0){std::cerr << "accept: fail" << std::endl;continue;}std::cout << "accept done" << std::endl;// 提取请求方的套接字信息uint16_t peer_port = ntohs(peer.sin_port);std::string peer_ip = inet_ntoa(peer.sin_addr);// 打印请求方的套接字信息std::cout << "accept: " << peer_ip << " [" << peer_port << "]" << std::endl;// 使用线程池技术提供服务Task t(netCal, server_sockfd);_tp->push(t);}}private:std::string _ip;uint16_t _port;int _listen_sockfd;threadpool<Task>* _tp; // 线程池的引入
};int main()
{tcpServer server(8081);server.init();server.loop();return 0;
}

测试结果在这里插入图片描述
若读取需要进行测试,关于服务端与客户端通信的其他文件,我放在了我的gitee中,需要自取

相关文章:

Linux | 网络通信 | 序列化和反序列化的讲解与实现

文章目录为什么要序列化&#xff1f;协议的实现服务端与客户端代码实现为什么要序列化&#xff1f; 由于默认对齐数的不同&#xff0c;不同的平台对相同数据进行内存对齐后&#xff0c;可能得到不同的数据。如果直接将这些数据进行网络传输&#xff0c;对方很可能无法正确的获…...

C#的委托原理刨析and事件原理刨析和两者的比较

什么是委托委托是一种引用类型&#xff0c;表示对具有特定参数列表和返回类型的方法的引用。 在实例化委托时&#xff0c;你可以将其实例与任何具有兼容参数和返回类型的方法进行绑定。 你可以通过委托实例调用方法。简单的理解&#xff0c;委托是方法的抽象类&#xff0c;它定…...

Redis学习【8】之Redis RDB持久化

文章目录Redis 持久化1 持久化基本原理2 RDB(Redis DataBase) 持久化2.1 持久化的执行2.2 手动 save 命令2.3 手动 bgsave 命令2.4 自动条件触发2.5 查看持久化时间3 RDB 优化配置3.1 save3.2 stop-write-on-bgsave-error3.3 rdbcompression3.4 rdbchecksum3.5 sanitize-dump-p…...

SpringSecurity认证

文章目录登陆校验流程依赖yaml实现建表、工具类、实体类加密器、AuthenticationManager登录逻辑登录过滤器、配置过滤器登出登陆校验流程 认证 登录&#xff1a; ​ ①自定义登录接口 ​ 调用ProviderManager的方法进行认证 如果认证通过生成token&#xff0c;根据userId把用…...

Socket套接字

概念 Socket套接字&#xff0c;是由系统提供用于网络通信的技术&#xff0c;是基于TCP/IP协议的网络通信的基本操作单元。基于Socket套接字的网络程序开发就是网络编程。 分类 Socket套接字主要针对传输层协议划分为如下三类&#xff1a; 流套接字&#xff1a;使用传输层TCP…...

mysql详解之innoDB

索引 Mysql由索引组织&#xff0c;所以索引是mysql多重要概念之一。 聚簇索引 InnoDB和MyISAm一样都是采用B树结构&#xff0c;但不同点在于InnoDB是聚簇索引&#xff08;或聚集索引&#xff09;&#xff0c;将数据行直接放在叶子节点后面。 这里可能存在一个误区&#xff1…...

电信运营商的新尝试:探索非通信领域的发展

近年来&#xff0c;随着电信运营商竞争的日趋激烈和网络建设的成本不断攀升&#xff0c;许多电信运营商已经开始缩减IT投资。然而&#xff0c;在如此情况下&#xff0c;电信运营商仍然需要寻找新的增长机会。那么&#xff0c;在持续缩减IT投资的情况下&#xff0c;电信运营商可…...

第07章_单行函数

第07章_单行函数 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 1. 函数的理解 1.1 什么是函数 函数在计算机语言的使用中贯穿始终&#xff0c;函数的作用是什么呢&#xff1f;它可以把我们经…...

Echarts实现多柱状图重叠重叠效果

有两种重叠效果: 1. 多个柱子重叠为一个 2. 多个柱子重叠为两组 第一种,图例: 这个灰色不是阴影哦, 是柱子. 1. 使用详解 (1) series.Z 折线图组件的所有图形的 z 值。控制图形的前后顺序。 z 值小的图形会被 z 值大的图形覆盖。z 相比 zlevel 优先级更低&#xff0c;而且不会…...

PHP学习笔记(一谦四益)

前言 上一篇文章 PHP学习笔记&#xff08;观隅反三&#xff09;分享了数组的知识&#xff0c;这篇文章接着分享和数组相关的算法。 算法效率 算法效率分为两种&#xff1a;第一种是时间效率&#xff0c;第二种是空间效率。时间效率被称为时间复杂度&#xff0c;而空间效率被称…...

Jvm -堆对象的划分

堆对于一个jvm进程来说是唯一的&#xff0c;一个进程只有一个jvm&#xff0c;但是进程半酣多个线程&#xff0c;多个线程共享一个堆。 也就是说&#xff0c;一个jvm实例只存在一个堆&#xff0c;同时对也是Java内存管理的核心区域。 Java堆区域的大小在jvm启动时就已经被确定…...

2023美赛F题讲解+数据领取

我们给大家准备了F题的数据&#xff0c;免费领取&#xff01;在文末 国内生产总值(GDP)可以说是一个国家经济健康状况最著名和最常用的指标之--。它通常用于确定一个国家的购买力和获得贷款的机会,为各国提出提高GDP的政策和项目提供动力。GDP“衡量一个国家在给定时间段内生产…...

【博客625】keepalived开启garp refresh的重要性

keepalived开启garp refresh的重要性 1、场景 1-1、对keepavlied master机器热迁移后出现vip不通&#xff0c;过后恢复 原因&#xff1a;机器迁移后网关那边的arp表没有刷新&#xff0c;流量还是转发到老的端口&#xff0c;但是机器已经迁移到别的端口了&#xff0c;于是网络…...

nginx防护规则,拦截非法字符,防止SQL注入、防XSS,nginx过滤url访问,屏蔽垃圾蜘蛛,WordPress安全代码篇

nginx防护规则,拦截非法字符,防止SQL注入、防XSS,nginx过滤url访问,屏蔽垃圾蜘蛛,WordPress安全代码篇 精心强化,小白一键复制 资源宝分享:www.httple.net 宝塔为例:/www/server/panel/vhost/nginx/你的网站域名.conf,复制代码点击保存 修改www.xx.net你自己域名incl…...

【计算机网络】网络层

文章目录网络层概述网络层提供的两种服务IPv4地址IPv4地址概述分类编址的IPv4地址划分子网的IPv4地址无分类编址的IPv4地址IPv4地址的应用规划IP数据报的发送和转发过程静态路由配置及其可能产生的路由环路问题路由选择路由选择协议概述路由信息协议RIP的基本工作原理开放最短路…...

产品经理知识体系:1.什么是互联网思维?

互联网思维 思考 笔记 用户思维 是要注重用户体验&#xff0c;产品带给用户的价值是什么&#xff0c;是能帮助用户获取想要的商品、解决生活中的问题、获取想要的信息&#xff0c;还是产品能通过兜售参与感、满足感等来满足用户的心理需求。 贯穿产品的整个生命周期过程。 简…...

【数据结构】单链表的接口实现(附图解和源码)

单链表的接口实现&#xff08;附图解和源码&#xff09; 文章目录单链表的接口实现&#xff08;附图解和源码&#xff09;前言一、定义结构体二、接口实现&#xff08;附图解源码&#xff09;1.开辟新空间2.头插数据3.头删数据4.打印整个单链表5.尾删数据6.查找单链表中的数据7…...

TikTok话题量超30亿,这款承载美好记忆的剪贴簿引发讨论

回忆风剪贴簿在TikTok引起关注小超在浏览超店有数后台时发现&#xff0c;有一款平平无奇的剪贴簿的种草视频爆火&#xff0c;在24h内收获了9.9K点赞&#xff0c;播放量更是突破了100W&#xff0c;直接冲到了【种草视频飙升榜】第六名的位置&#xff0c;并且这个数字目前仍在继续…...

了解Dubbo

1.注册中心挂了&#xff0c;消费者还能不能调用生产者&#xff1f; 注册中心挂了&#xff0c; 消费者依然可以调用生产者。生产者和消费者都会在本地缓存注册中心的服务列表&#xff0c;当注册中心宕机时&#xff0c;消费者会读取本地的缓存数据&#xff0c;直接访问生产者&am…...

2023年前端面试知识点总结(JavaScript篇)

近期整理了一下高频的前端面试题&#xff0c;分享给大家一起来学习。如有问题&#xff0c;欢迎指正&#xff01; 1. JavaScript有哪些数据类型 总共有8种数据类型&#xff0c;分别是Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt Null 代表的含义是空对象…...

jQuery

文章目录jQuery 介绍初体验核心函数jQuery 对象和 dom 对象区分什么是 jQuery 对象&#xff0c;什么是 dom 对象问题&#xff1a;jQuery 对象的本质是什么&#xff1f;jQuery 对象和 Dom 对象使用区别Dom 对象和 jQuery 对象互转&#xff08;重点&#xff09;jQuery 选择器&…...

强化学习基础概念

强化学习入门 入门学习第一周&#xff1a;基础概念 经验回放&#xff1a; 将sss,agent当前步的action环与境的交互rrr以及下一步的状态st1s_{t1}st1​组成的四元组[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wxhVd0dn-1676710992983)(null)] 组…...

Redis学习【9】之Redis RDB持久化

文章目录一 AOF(Append Only File) 持久化二 AOF 基础配置2.1 AOF的开启2.2 文件名配置2.3 混合式持久化开启2.4 AOF 文件目录配置三 AOF 文件格式3.1 Redis 协议3.2 查看 AOF 文件3.3 清单文件3.4 Rewrite 机制3.4.1 rewrite简介3.4.2 rewrite 计算策略3.4.3 手动开启 rewrite…...

分析 vant4 源码,学会用 vue3 + ts 开发毫秒级渲染的倒计时组件,真是妙啊

2022年11月23日首发于掘金&#xff0c;现在同步到公众号。11. 前言大家好&#xff0c;我是若川。推荐点右上方蓝字若川视野把我的公众号设为星标。我倾力持续组织了一年多源码共读&#xff0c;感兴趣的可以加我微信 lxchuan12 参与。另外&#xff0c;想学源码&#xff0c;极力推…...

事件驱动型架构

事件驱动型架构是一种软件设计模式&#xff0c;其中微服务会对状态变化&#xff08;称为“事件”&#xff09;作出反应。事件可以携带状态&#xff08;例如商品价格或收货地址&#xff09;&#xff0c;或者事件也可以是标识符&#xff08;例如&#xff0c;订单送达或发货通知&a…...

20222023华为OD机试 - 不含 101 的数(Python)

不含 101 的数 题目 小明在学习二进制时,发现了一类不含 101 的数, 也就是将数字用二进制表示,不能出现 101 。 现在给定一个正整数区间 [l,r],请问这个区间内包含了多少个不含 101 的数? 输入 输入一行,包含两个正整数 l l l, r r r...

杭州电子科技大学2023年MBA招生考试成绩查询和复查申请的通知

根据往年的情况&#xff0c;2023杭州电子大学MBA考试初试成绩可能将于2月21日公布&#xff0c;最早于20号出来&#xff0c;为了广大考生可以及时查询到自己的分数&#xff0c;杭州达立易考教育为大家汇总了信息。根据教育部和浙江省教育考试院关于硕士研究生招生考试工作的统一…...

电子技术——CS和CE放大器的高频响应

电子技术——CS和CE放大器的高频响应 在绘制出MOS和BJT的高频响应模型之后&#xff0c;我们对MOS和BJT的高频响应有了进一步的认识。现在我们想知道的是在高频响应中 fHf_HfH​ 的关系。 高频响应分析对电容耦合还是直接耦合都是适用的&#xff0c;因为在电容耦合中高频模式下…...

2023年数学建模美赛D题(Prioritizing the UN Sustainability Goals):SDGs 优先事项的选择

正在写&#xff0c;不断更新&#xff0c;别着急。。。 4. SDGs 优先事项的选择 4.1 基于SDG密度分布图选择优先事项 虽然每个可持续发展目标的接近度矩阵和中心性度量的结果是通用的&#xff0c;并创建了基本的可持续发展目标网络&#xff0c;但由于各国在网络的不同部分取得…...

springboot实现项目启动前的一些操作

在服务启动时&#xff0c;做一些操作&#xff0c;比如加载配置&#xff0c;初始化数据&#xff0c;请求其他服务的接口等。 有三种方法&#xff1a; 第一种是实现CommandLineRunner接口 第二种是实现ApplicationRunner接口 第三种是使用注解&#xff1a;PostConstruct 三者使用…...

做智慧教室的网站/竞价托管

使用Guice&#xff0c;需要添加第三方的包&#xff08;guice-3.0.jar和javax.inject.jar&#xff09; 链接&#xff1a;http://pan.baidu.com/s/1nuMjYOT 密码&#xff1a;1soo 将包导入MyEclipse或eclipse的方法&#xff1a;http://jingyan.baidu.com/article/6079ad0e7e4de12…...

做设计的地图网站/网站怎样优化文章关键词

ZMQ (以下 ZeroMQ 简称 ZMQ)是一个简单好用的传输层&#xff0c;像框架一样的一个 socket library&#xff0c;他使得 Socket 编程更加简单、简洁和性能更高。是一个消息处理队列库&#xff0c;可在多个线程、内核和主机盒之间弹性伸缩。ZMQ 的明确目标是“成为标准网络协议栈的…...

西安网站建设咪豆互联/互联网营销课程体系

此函数允许创建方向强度直方图&#xff0c;也称为“风玫瑰”。这个工具可以用来表示这种图形。 This function allows to create a Direction-intensity histogram, also known as “Wind Roses”. This tool can be used for representing this kind of graphics. 它还能够将…...

wordpress文章禁止搜索/百度网盘电脑版官网

OpenFeign 概述 Feign是一个声明式的web服务客户端&#xff0c;让编写web服务客户端变得非常容易&#xff0c;只需创建一个接口并在接口上添加Feign的注解即可 为什么要使用OpenFeign 前面在使用Ribbon RestTemplate时&#xff0c;利用RestTemplate对http请求进行了封装处理&…...

做公寓酒店跟网站合作有什么技巧/b站推广app大全

我发现童鞋们对百度的技术很感兴趣哦&#xff0c;呵呵&#xff0c;大型互联网公司真是聚集牛人的地方&#xff0c;不过我感觉和google比起来&#xff08;看他们网站的源码&#xff09;&#xff0c;google的网页的代码比百度更加复杂&#xff0c;我以前觉得google什么都是开源&a…...

网址导航网站建设/廊坊自动seo

PIM 文件疑难解答常见的 PIM 打开问题Avid Pro Tools 不在你尝试加载 PIM 文件并收到错误&#xff0c;例如 “%%os%% 无法打开 PIM 文件扩展名”。 发生这种情况时&#xff0c;通常是由于 %%os%% 中缺少 Avid Pro Tools。 通过双击打开 PIM 的典型路径将不起作用&#xff0c;因…...