协议的定制之序列化与反序列化 | 守护进程

目录

一、再谈协议

二、序列化与反序列化

三、网络计算器的简单实现

四、网络计算器完整代码

五、代码改进

六、守护进程

七、Json序列化与反序列化

八、netstat


一、再谈协议

是对数据格式和计算机之间交换数据时必须遵守的规则的正式描述。简单的说了,网络中的计算机要能够互相顺利的通信,就必须讲同样的语言,语言就相当于协议。

为了使数据在网络上能够从源主机到达目的主机,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议。只有使用相同的协议,主机间才能进行通信。 

二、序列化与反序列化

今天,我们作为客户端,想要让服务端帮助我们进行计算,然后将结果返回给我们,这就是一个网络计算器了。如果我们要计算两数相加,比如 a+b,那么现在问题来了,我们应该如何将a,+,b这三个数据传输给服务器呢?是一个一个传呢,还是整体传输呢?

如果客户端将这些结构化的数据单独一个个的发送到网络当中,那么服务端从网络当中获取这些数据时也只能一个个获取,此时服务端还需要考虑,哪个是左操作数,哪个是操作符,哪个是右操作数。所以这样不合适。

对于一起发送。a,+,b三个数据组成一组结构化的数据,我们首先想到使用一个结构体将三个数据打包,一起发送给服务器,而我们所用的各种通信函数只允许我们传输字符串,所以我们必须要将结构化的数据先转化成字符串,然后才能发送给服务器。而服务器接收到字符串后,则必须将字符串进行解析,转成结构化的数据,进行计算,然后将结果转成字符串发回客户端。这个过程就叫做“序列化”和“反序列化”。

~ 序列化:就是将对象的状态信息转换为可以存储或传输的形式(字节序列)的过程。

~ 反序列化:就是把字节序列恢复为对象的过程。

具体怎么实现,我们通过编写网络计算器来进行讲解。

三、网络计算器的简单实现

对于网络计算器的客户端,我们使用一个结构体去存储左右操作数和操作符,这个结构体我们称为请求(Request),在向服务端发送请求的时候,我们需要转成字符串才能发送,于是我们需要有序列化的函数,当然,收到结果后,我们也需要反序列化函数,将字符串结果转成结构化结果。

对于服务端,我们使用一个结构体去存储计算结果和标识计算结果的正确性,因为客户端可能会发过来除0或者模0的请求。在收到客户端的请求后,我们需要反序列化函数将结果转成结构化数据,方便计算,发回结果的时候,需要序列化函数将结果转成字符串才能发送。

四、网络计算器完整代码

Sock.hpp

我们对网络套接字进行一个封装。

#pragma once
#include <iostream>
#include <string>
#include <cstdbool>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>class Sock
{
public:const static int gmv = 20;Sock(){}int Socket(){// 1.创建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cout << "创建套接字失败!" << std::endl;exit(1);}std::cout << "创建套接字成功!" << std::endl;return sock;}void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0"){// 2.进行绑定struct sockaddr_in src_server;bzero(&src_server, sizeof(src_server));src_server.sin_family = AF_INET;src_server.sin_port = htons(port);inet_pton(AF_INET, ip.c_str(), &src_server.sin_addr);socklen_t len = sizeof(src_server);if (bind(sock, (struct sockaddr *)&src_server, len) < 0){std::cout << "绑定失败!" << std::endl;exit(2);}std::cout << "绑定成功!" << std::endl;}void Listen(int sock){// 3.开始监听,等待连接if (listen(sock, gmv) < 0){std::cout << "监听失败!" << std::endl;exit(3);}std::cout << "服务器监听成功!" << std::endl;}int Accept(int sock, std::string *ip, uint16_t *port){// 4.获取链接struct sockaddr_in client_sock;socklen_t len = sizeof(client_sock);int serversock = accept(sock, (struct sockaddr *)&client_sock, &len);if (serversock < 0){std::cout << "获取链接失败!" << std::endl;return -1;}if (port)*port = ntohs(client_sock.sin_port);if (ip)*ip = inet_ntoa(client_sock.sin_addr);return serversock;}bool Connect(int sock, const std::string &ip, const uint16_t &port){struct sockaddr_in server;memset(&server, 0, sizeof server);server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(ip.c_str());server.sin_port = htons(port);if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == 0)return true;elsereturn false;}~Sock(){}
};

TcpServer.hpp: 

#pragma once
#include "Sock.hpp"
#include <functional>
#include <pthread.h>using func_t = std::function<void(int)>;class TcpServer;
class threaddata
{
public:threaddata(int sock, TcpServer *server) : sock_(sock), server_(server) {}~threaddata() {}public:int sock_;TcpServer *server_;
};class TcpServer
{
private:static void *threadRoutine(void *arg){pthread_detach(pthread_self());threaddata *td = (threaddata *)arg;td->server_->excute(td->sock_);close(td->sock_);delete td;}public:TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0"){listensock_ = sock_.Socket();sock_.Bind(listensock_, port, ip);sock_.Listen(listensock_);}void BindServer(func_t func){func_ = func;}void excute(int sock){func_(sock);}void Start(){for (;;){std::string clientip;uint16_t clientport;int sock = sock_.Accept(listensock_, &clientip, &clientport);if (sock == -1)continue;threaddata *td = new threaddata(sock, this);pthread_t tid;pthread_create(&tid, nullptr, threadRoutine, td);}}~TcpServer(){if (listensock_ >= 0)close(listensock_);}private:int listensock_;Sock sock_;func_t func_;
};

Protocol.hpp:协议定制

我们规定序列化的结果是"x_ op_ y_”,即:左操作数,空格,操作符,空格,左操作数。

#pragma once
#include <iostream>
#include <string>
#include <cstdbool>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>#define SPACE " "
#define SPACELEN strlen(SPACE)std::string Recv(int sock)
{char buffer[1024];ssize_t s = recv(sock, buffer, sizeof buffer, 0);if (s == 0)std::cout << "客户端退出!" << std::endl;else if (s > 0)return buffer;elsestd::cerr << "客户端退出!" << std::endl;return "";
}void Send(int sock, const std::string &str)
{ssize_t s = send(sock, str.c_str(), str.size(), 0);
}class Request
{
public:std::string Serialize(){std::string requeststr = std::to_string(x_);requeststr += SPACE;requeststr += op_;requeststr += SPACE;requeststr += std::to_string(y_);return requeststr;}// x_ op_ y_bool Deserialization(const std::string &str){std::size_t left = str.find(SPACE);if (left == std::string::npos)return false;std::size_t right = str.rfind(SPACE);if (right == std::string::npos)return false;x_ = atoi(str.substr(0, left).c_str());y_ = atoi(str.substr(right + SPACELEN).c_str());op_ = str[left + SPACELEN];return true;}public:Request() {}Request(const int &x, const int &y, const char &op) : x_(x), y_(y), op_(op) {}~Request() {}public:int x_;int y_;char op_;
};class Response
{
public:// code_ result_std::string Serialize(){std::string str = std::to_string(code_);str += SPACE;str += std::to_string(result_);return str;}bool Deserialization(std::string &str){std::size_t pos = str.find(SPACE);if (pos == std::string::npos)return false;code_ = atoi(str.substr(0, pos).c_str());result_ = atoi(str.substr(pos + SPACELEN).c_str());return true;}public:Response(const int &result = 0, const int &code = 0) : result_(result), code_(code) {}~Response() {}public:int result_;int code_;
};

CalServer.cc:

#include "tcpserver.hpp"
#include "Protocol.hpp"
#include <signal.h>
#include <memory>static void usage(std::string proc)
{std::cout << proc << " port" << std::endl;
}Response CalculatorHelper(const Request &req)
{Response res(0, 0);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 (0 == req.y_)res.code_ = 1;elseres.result_ = req.x_ / req.y_;break;case '%':if (0 == req.y_)res.code_ = 2;elseres.result_ = req.x_ % req.y_;break;default:res.code_ = 3;break;}return res;
}void Calculator(int sock)
{while (true){std::string buffer = Recv(sock);if (!buffer.empty()){Request req;req.Deserialization(buffer);Response res = CalculatorHelper(req);std::string sendstr = res.Serialize();Send(sock, sendstr);}else break;}
}int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(1);}signal(SIGPIPE, SIG_IGN);uint16_t server_port = atoi(argv[1]);std::unique_ptr<TcpServer> sev(new TcpServer(server_port));sev->BindServer(Calculator);sev->Start();return 0;
}

CalClient.cc:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <unistd.h>
#include "Sock.hpp"
#include "Protocol.hpp"static void usage(std::string proc)
{std::cout << proc << " ip port" << std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(1);}std::string ip = argv[1];uint16_t port = atoi(argv[2]);Sock sock;int sockfd = sock.Socket();if (!sock.Connect(sockfd, ip, port)){std::cout << "连接出错!" << std::endl;exit(2);}std::cout << "连接成功!" << std::endl;while (true){Request req;std::cout << "请输入x # ";std::cin >> req.x_;std::cout << "请输入y # ";std::cin >> req.y_;std::cout << "请输入op # ";std::cin >> req.op_;std::string sendstr = req.Serialize();Send(sockfd, sendstr);std::string recstr = Recv(sockfd);Response res;res.Deserialization(recstr);std::cout << "code_: " << res.code_ << std::endl;std::cout << "result_: " << res.result_ << std::endl;}return 0;
}

运行结果:

五、代码改进

其实,上面我们 Protocol.hpp 中的代码是有问题的?为什么呢?

我们早就说过,TCP是面向字节流的,所以在从网络中读取的时候,并不能保证发送和读取到的是一个完整的 x_ op_ y_ 结构,可能只发送或者读取了 x_ op_,也可能是 x_ op_ y_  x_ op_ y_  x_ op_ y_。所以客户端和服务器不能准确区分,因此我们需要对发送和读取进行控制,使得每次发送和读取到的都是一个完整的报文。

我们所使用的TCP协议是传输层协议。在TCP层,拥有两个缓冲区:发送缓冲区和接收缓冲区。我们调用的所有发送函数,并不是直接把数据发送到网络中,而是将数据由应用层拷贝到TCP的发送缓冲区中,由TCP协议决定如何发送这些数据和每次发送多少数据。接收函数也不是直接从网络中获取数据,而是从发送缓冲区拷贝数据到应用层。至于数据如何到TCP的接收缓冲区,也是完全由TCP协议决定。

因此,发送函数和接收函数本质上是拷贝函数。

因此我们可以将序列化的数据定成这种结构  length\r\nx_ op_ y_\r\n(或者 length\r\ncode_ result_\r\n) ,我们通过length来标定正文长度,使用\r\n来分隔length和正文。

知道length,就可以知道怎样读取多长的数据了,这样就可以读取到完整的报文。而发送的时候任然是面向字节流式的发送,不过我们需要添加 length 和特殊符号 \r\n,再发送。

在Protocol.hpp:协议定制里面,我们还需要修改下面的代码:Encode:将序列化的字符串转成  length\r\nx_ op_ y_\r\n。Decode:将 length\r\nx_ op_ y_\r\n 转成 x_ op_ y_(即拿到正文)。

#pragma once
#include <iostream>
#include <string>
#include <cstdbool>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>#define SPACE " "
#define SPACELEN strlen(SPACE)
#define SEP "\r\n"
#define SEPLEN strlen(SEP)bool Recv(int sock, std::string *out)
{char buffer[1024];ssize_t s = recv(sock, buffer, sizeof buffer - 1, 0);if (s == 0){std::cout << "客户端退出!" << std::endl;return false;}else if (s > 0){buffer[s] = 0;*out += buffer;}else{std::cerr << "客户端退出!" << std::endl;return false;}return true;
}void Send(int sock, const std::string &str)
{ssize_t s = send(sock, str.c_str(), str.size(), 0);
}// len\r\nx op y\r\n
std::string Decode(std::string buffer)
{std::size_t pos = buffer.find(SEP);if (pos == std::string::npos)return "";int size = atoi(buffer.substr(0, pos).c_str());  // 完整正文的长度int mainsize = buffer.size() - pos - 2 * SEPLEN; // 正文长度if (mainsize >= size){buffer.erase(0, pos + SEPLEN);std::string result = buffer.substr(0, size);buffer.erase(0, size + SEPLEN);return result;}elsereturn "";
}std::string Encode(std::string &s)
{std::string ret = std::to_string(s.size());ret += SEP;ret += s;ret += SEP;return ret;
}class Request
{
public:std::string Serialize(){std::string requeststr = std::to_string(x_);requeststr += SPACE;requeststr += op_;requeststr += SPACE;requeststr += std::to_string(y_);return requeststr;}// x_ op_ y_bool Deserialization(const std::string &str){std::size_t left = str.find(SPACE);if (left == std::string::npos)return false;std::size_t right = str.rfind(SPACE);if (right == std::string::npos)return false;x_ = atoi(str.substr(0, left).c_str());y_ = atoi(str.substr(right + SPACELEN).c_str());op_ = str[left + SPACELEN];return true;}public:Request() {}Request(const int &x, const int &y, const char &op) : x_(x), y_(y), op_(op) {}~Request() {}public:int x_;int y_;char op_;
};class Response
{
public:// code_ result_std::string Serialize(){std::string str = std::to_string(code_);str += SPACE;str += std::to_string(result_);return str;}bool Deserialization(std::string &str){std::size_t pos = str.find(SPACE);if (pos == std::string::npos)return false;code_ = atoi(str.substr(0, pos).c_str());result_ = atoi(str.substr(pos + SPACELEN).c_str());return true;}public:Response(const int &result = 0, const int &code = 0) : result_(result), code_(code) {}~Response() {}public:int result_;int code_;
};

CalServer.cc:

#include "tcpserver.hpp"
#include "Protocol.hpp"
#include <signal.h>
#include <memory>static void usage(std::string proc)
{std::cout << proc << " port" << std::endl;
}Response CalculatorHelper(const Request &req)
{Response res(0, 0);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 (0 == req.y_)res.code_ = 1;elseres.result_ = req.x_ / req.y_;break;case '%':if (0 == req.y_)res.code_ = 2;elseres.result_ = req.x_ % req.y_;break;default:res.code_ = 3;break;}return res;
}void Calculator(int sock)
{std::string str;while (true){bool rest = Recv(sock, &str);if (!rest)break;std::string package = Decode(str);if (!package.empty()){Request req;req.Deserialization(package);Response res = CalculatorHelper(req);std::string sendstr = res.Serialize();sendstr = Encode(sendstr);Send(sock, sendstr);}elsecontinue;}
}int main(int argc, char *argv[])
{if (argc != 2){usage(argv[0]);exit(1);}signal(SIGPIPE, SIG_IGN);uint16_t server_port = atoi(argv[1]);std::unique_ptr<TcpServer> sev(new TcpServer(server_port));sev->BindServer(Calculator);sev->Start();return 0;
}

 CalClient.cc:

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <cstdlib>
#include <unistd.h>
#include "Sock.hpp"
#include "Protocol.hpp"static void usage(std::string proc)
{std::cout << proc << " ip port" << std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(1);}std::string ip = argv[1];uint16_t port = atoi(argv[2]);Sock sock;int sockfd = sock.Socket();if (!sock.Connect(sockfd, ip, port)){std::cout << "连接出错!" << std::endl;exit(2);}std::cout << "连接成功!" << std::endl;bool quit = false;std::string buffer;while (!quit){Request req;std::cout << "请输入x # ";std::cin >> req.x_;std::cout << "请输入y # ";std::cin >> req.y_;std::cout << "请输入op # ";std::cin >> req.op_;std::string sendstr = req.Serialize();sendstr = Encode(sendstr);Send(sockfd, sendstr);while (true){bool ret = Recv(sockfd, &buffer);if (!ret){quit = true;break;}std::string recstr = Decode(buffer);if (recstr.empty())continue;Response res;res.Deserialization(recstr);std::cout << "code_: " << res.code_ << std::endl;std::cout << "result_: " << res.result_ << std::endl;break;}}close(sockfd);return 0;
}

六、守护进程

后台进程:就是在后台运行,不占用用户终端的进程。

前台进程:前台进程是与用户直接交互的进程(和终端关联的进程)。可以直接获取键盘的输入。

如下:我们的bash就是一个最常见的前台进程,我们输入各种指令,他就能返回相应的结果。

而我们上面所写的服务器进程在启动后,也是在前台运行的,也是一个前台进程。如下:

从上图我们也看到:服务器进程在启动后,如果我们再输入各种指令的话,将不会有任何结果。因为xshell登录后,只允许有一个前台进程和多个后台进程。 

再如下:我们使用管道创建多个进程,

除了PID,PPID之外,PGID我们称为组ID,SID我们称为会话ID。

这三个被同时创建的进程组成了一个进程组,他们的PGID都是23440,也是第一个进程的PID。所以一个进程组的PGID是第一个进程的PID。

我们在登陆了xshell后,xshell会给用户提供一种会话机制,在会话中,包含了给用户提供服务的bash进程,终端,以及用户自己在该会话中启动的进程。当xshell退出登录,会话也会退出。

如下图:

而现在,我希望会话退出后,服务器进程任然也可以运行。那么我们就需要让服务器进程自成一个会话。这样服务器进程就成为了一个守护进程。守护进程也是一种后台进程。

我们使用 setsid()函数,就可以让某个进程变成自成会话。注:setsid要成功被调用,必须保证当前进程不是进程组的组长。

所以,我们自己写一个方法来让服务器进程变成一个守护进程。

一般步骤:

1、忽略信号:SIGPIPE,SIGCHLD

2、不要让自己是进程组的组长,fork()

3、调用setsid()函数

4、因为守护进程不能向显示器打印消息,所以我们需要将标准输出、标准错误和标准输入进行重定向。这里我们需要使用Linux中的一个文件:/dev/null。其特点就是,任何向其中写入的内容都会被其丢弃。因为文件中没有内容,所以读取时什么也读取不到。

daemon.hpp:

#pragma once
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>void mydaemon()
{// 1.忽略信号signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);// 2.不要让自己成为组长if (fork() > 0)exit(0);// 3.调用setsid()setsid();int devnull = open("/dev/null", O_RDONLY | O_WRONLY);if (devnull > 0){dup2(0, devnull);dup2(1, devnull);dup2(2, devnull);close(devnull);}
}

然后,我们只需要在服务器运行前调用这个函数即可。当然,服务器代码中任何向显示器打印的函数都不应该调用了。

CalServer.cc的main函数:

通过查询,发现 MyServer的父进程PPID为1,也就是操作系统。所以说,守护进程也是一种孤儿进程。 

七、Json序列化与反序列化

对于序列化和反序列化,上面我们自己的方案其实也很多问题,所以我们除了可以自定义方案以外,还可以使用别人的已经实现好了的方案。比如我们接下来讲到的:json。

使用前,我们需要安装json库: sudo yum install jsoncpp-devel

Protocol.hpp:

#include <jsoncpp/json/json.h>class Request
{
public:std::string Serialize(){Json::Value root;root["x"] = x_;root["y"] = y_;root["op"] = op_;Json::FastWriter writer;return writer.write(root);}// x_ op_ y_bool Deserialization(const std::string &str){Json::Value root;Json::Reader reader;reader.parse(str, root);x_ = root["x"].asInt();y_ = root["y"].asInt();op_ = root["op"].asInt();return true;}public:Request() {}Request(const int &x, const int &y, const char &op) : x_(x), y_(y), op_(op) {}~Request() {}public:int x_;int y_;char op_;
};class Response
{
public:// code_ result_std::string Serialize(){Json::Value root;root["code"] = code_;root["result"] = result_;Json::FastWriter writer;return writer.write(root);}bool Deserialization(std::string &str){Json::Value root;Json::Reader reader;reader.parse(str, root);code_ = root["code"].asInt();result_ = root["result"].asInt();return true;}public:Response(const int &result = 0, const int &code = 0) : result_(result), code_(code) {}~Response() {}public:int result_;int code_;
};

八、netstat

netstat:我们可以通过netstat命令来查看当前网络的状态,这里我们可以选择携带nlup选项。

netstat常用选项:

-n:直接使用IP地址,而不通过域名服务器。
-l:显示监控中的服务器的Socket。
-t:显示TCP传输协议的连线状况。
-u:显示UDP传输协议的连线状况。
-p:显示正在使用Socket的程序识别码和程序名称。

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

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

相关文章

FFmpeg常用实例详解

FFmpeg 是一个专业的多媒体框架&#xff0c;能够解码、编码、转码、复用、解复用、流式传输、过滤和播放几乎所有格式的媒体文件。 这里通过一些示例简单地介绍下 ffmpeg 命令的基本使用。 一、获取详细信息 ffmpeg -i <inputfile> -hide_banner 其中 -hide_banner 选项…

佛山南海区桂城珠宝玉石电商协会举办2023年度电商企业颁奖典礼

4月24日&#xff0c;佛山市南海区桂城珠宝玉石电商协会隆重举办第一届三次会员大会暨2023年度电商企业颁奖典礼&#xff0c;广邀各级政府领导、行业组织、珠宝商场、电商企业、珠宝直播达人以及新闻媒体嘉宾&#xff0c;共见璀璨&#xff0c;共话新发展、新机遇。这是平洲玉器珠…

Java-GUI-AWT-组件-TextComponent类

1 需求 2 接口 java.lang.Object java.awt.Component java.awt.TextComponent Method Detail public void setText(String t)public String getText()public String getSelectedText()public boolean isEditable()public void setEditable(boole…

docker 启动时报错

docker 启动时报如下错误 Job for docker.service failed because the control process exited with error code. See "systemctl status docker.service" and "journalctl -xe" for details 因为安装docker时添加了镜像源 解决方案&#xff1a; mv /etc/…

汉译英早操练-(十八)

hello大家好&#xff0c;文接上回&#xff1a;https://blog.csdn.net/weixin_41953346/article/details/138184776 继续学习政府工作报告。 财政政策加力提 效&#xff0c;加强重点领域支出保障&#xff0c;全年新增税费优惠超过2.2万亿元&#xff0c;增发 1 万亿元国债支持灾 …

pwn--realloc [CISCN 2019东南]PWN5

首先学习一下realloc这个函数&#xff0c;以下是文心一言的解释&#xff1a; realloc是C语言库函数之一&#xff0c;用于重新分配内存空间。它的主要功能是调整一块内存空间的大小。当需要增加内存空间时&#xff0c;realloc会分配一个新的更大的内存块&#xff0c;然后将原内…

冯唐成事心法笔记 —— 知世

系列文章目录 冯唐成事心法笔记 —— 知己 冯唐成事心法笔记 —— 知人 冯唐成事心法笔记 —— 知世 冯唐成事心法笔记 —— 知智慧 文章目录 系列文章目录PART 3 知世 成事者的自我修养怎样做一个讨人喜欢的人第一&#xff0c;诚心第二&#xff0c;虚心 如何正确看待别人的评…

C#基础|OOP学习总结、优质的OOP程序有啥特点。

哈喽&#xff0c;你好&#xff0c;我是雷工。 以下为关于学习OOP的学习笔记。 01 OOP学习与基础语法有何不同 C#基础语法需要当时记住就行&#xff1b;OOP学习需要深入理解和记忆。 02 OOP学什么&#xff1f; OOP是学习各种编程的原则、方法、技巧、经验、模式、架构等。 …

超越边界:如何ChatGPT 3.5、GPT-4、DALL·E 3和Midjourney共同重塑创意产业

KKAI&#xff08;kkai人工智能&#xff09;是一个整合了多种尖端人工智能技术的多功能助手平台&#xff0c;融合了OpenAI开发的ChatGPT3.5、GPT4.0以及DALLE 3&#xff0c;并包括了独立的图像生成AI—Midjourney。以下是这些技术的详细介绍&#xff1a; **ChatGPT3.5**&#xf…

edge浏览器新建标签页闪退怎么解决?(打不开标签页)

文章目录 问题描述方法一方法二 问题描述 昨天开始出现这个问题&#xff0c;每次点击 打开一个新的标签页&#xff0c;马上就闪退了。 既然是新建标签页的问题&#xff0c;那么就在设置里看一下新建标签页发生了什么问题。 方法一 进入设置&#xff0c;会发现&#xff0c;有…

常用的 Spring Boot 注解及其作用

1、常用注解介绍&#xff1a; Spring Boot 提供了许多注解来简化开发&#xff0c;并帮助开发者在 Spring 应用中实现各种功能。以下是一些常用的 Spring Boot 注解及其作用&#xff1a; SpringBootApplication&#xff1a; 作用&#xff1a;用于标识主启动类&#xff0c;通常位…

信号分解 | SSA(奇异谱分析)-Matlab

分解效果 SSA(奇异谱分析) 信号分解 | SSA(奇异谱分析)-Matlab 奇异谱分析(Singular Spectrum Analysis,简称SSA)是一种用于时间序列分析的方法。它可以用于数据降维、信号分解、噪声去除和预测等应用。 SSA的基本思想是将时间序列分解为若干个成分,每个成分代表着不同的…

语言模型的发展

文章目录 语言模型的发展历程大语言模型的能力特点大语言模型关键技术概览大语言模型对科技发展的影响 语言模型的发展历程 一般来说&#xff0c;语言模型旨在对于人类语言的内在规律进行建模&#xff0c;从而准确预测词序列中未来&#xff08;或缺失&#xff09;词或词元&…

Dubbo应用可观测性升级指南与踩坑记录

应用从dubbo-3.1.*升级到dubbo-*:3.2.*最新稳定版本&#xff0c;提升应用的可观测性和度量数据准确性。 1. dubbo版本发布说明(可不关注) dubbo版本发布 https://github.com/apache/dubbo/releases 【升级兼容性】3.1 升级到 3.2 2. 应用修改点 应用一般只需要升级dubbo-s…

Vue+OpenLayers7入门到实战:OpenLayers加载GeoJson格式数据并解析成多边形、线段、点和区域范围等要素叠加到地图矢量图层上

返回《Vue+OpenLayers7》专栏目录:Vue+OpenLayers7入门到实战 前言 本章介绍如何使用OpenLayers7在地图上加载GeoJson格式数据并解析成多边形、线段、点和区域范围等要素叠加到地图矢量图层上的功能。 前面两章也是可以支持多边形、线段、点和区域范围灯数据加载的,只是没…

Go 到底是哪里没有做好,我请问呢?

没有引导好并发理念 从历史背景来看&#xff0c;在 Go 诞生的那个年代&#xff0c;并发编程是一个比较新颖的理念。许多其他编程语言、论文甚至书籍都写过关于并发编程的内容。并发编程还没有成为主流思想。 Go 团队发明了 “goroutine” 这个词&#xff0c;Go 让协程的使用变…

轴承轶闻01-扎雷茨基和帕姆格伦

这个口述历史项目是NASA的一个在线资源&#xff0c;旨在记录和保留NACA时期的历史资料和相关信息。NACA成立于1915年&#xff0c;直到1958年成立NASA之前一直存在。在这段时间里&#xff0c;NACA在航空技术和航天领域做出了许多重要贡献&#xff0c;为美国的航空航天事业奠定了…

Leetcode刷题之——队列Queue|先入先出FIFO|广度优先搜索BFS|栈Stack|后入先出LIFO|深度优先搜索DFS

Leetcode刷题之——队列Queue|先入先出FIFO|广度优先搜索BFS|栈Stack|后入先出LIFO|深度优先搜索DFS 1. 队列(Queue)——FIFO&#xff0c;先入先出的数据结构1.1 循环队列1.2 内置队列的常用方法&#xff08;C&#xff09;1.3 广度优先搜索&#xff08;BFS&#xff09; 2.栈(St…

袁庭新ES系列16节|Elasticsearch客户端高级操作

前言 上一章节袁老师主要带领大家学习了Elasticsearch客户端基础部分的内容&#xff0c;Elasticsearch客户端还有很多高级相关的操作&#xff0c;这一章节主要带领大家来学习Elasticsearch客户端高级相关的操作。接下来就跟上袁老师的节奏继续探讨Elasticsearch的相关知识。 一…

Linux常用命令总结(四):文件权限及相关命令介绍

1. 文件属性信息解读 1. 文件类型和权限的表示 0首位表示类型。在Linux中第一个字符代表这个文件是目录、文件或链接文件 符号对应文件类型-代表文件dd 代表目录l链接文档(link file)&#xff1b; 1-3位确定属主&#xff08;该文件的所有者&#xff09;拥有该文件的权限。 4-6…