Linux---自定义协议

应用层协议

一、协议定制---以网络计算器为例

网络计算机功能---进行+-*/^&|的运算并返回结果

请求和响应的结构体如下

// Protocol.hpp
#pragma once
#include <iostream>
#include <memory>
class Request
{
public:Request(){}Request(int data_x, int data_y, char op): _data_x(data_x), _data_y(data_y), _op(op){}private:int _data_x;int _data_y;char _op;
};
class Response
{
public:Response(){}Response(int result, int code): _result(result), _code(code){}private:int _result;int _code;
};// 建造类 --- 设计模式 --- 用来创建类对象
class Factory
{
public:std::shared_ptr<Request> BuiltRequest(){std::shared_ptr<Request> req = std::make_shared<Request>();return req;}std::shared_ptr<Request> BuiltRequest(int data_x, int data_y, char op){std::shared_ptr<Request> req = std::make_shared<Request>(data_x, data_y, op);return req;}std::shared_ptr<Response> BuiltResponse(){std::shared_ptr<Response> resp = std::make_shared<Response>();return resp;}std::shared_ptr<Response> BuiltResponse(int result, int code){std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);return resp;}
};

将请求的数据按照怎样的方式序列化成为字符串呢?

我们选择将数据转换成 "len\nx op y\n"

  • len是"x op y"的字符串长度,"len"后面的\n用来将len和后面的有效数据分开
  • ”x op y“是要计算的表达式,比如”5 + 6“,中间加空格间隔开,它后面的\n是为了调试打印,可以不加

这里我们也可以直接选择将数据转化成 "x op y\n",用\n作为数据之间的间隔,但是如果我们要传递的数据中就包含\n,这样做就会出现问题,但是加"len\n"就不会,因为我们能保证len这个字符串中不可能出现\n,更具有通用性

二、序列化和反序列化

所以,序列化和反序列化本质就是字符串相关的操作,具体的代码如下

//新增 给字符串数据,加报头和去报头的函数
const std::string BlankSep = " ";
const std::string LineSep = "\n";// 将"有效数据" ->  "len\n有效数据\n"
std::string Encode(const std::string &message)
{return std::to_string(message.size()) + LineSep + message + LineSep;
}// 将"len\n有效数据\n" ->  "有效数据",这里要注意判断数据是否完整,即是否有一个完整的请求
bool Decode(std::string &package, std::string *message)
{auto pos = package.find(LineSep);if (pos == std::string::npos)return false;int len = std::stoi(package.substr(0, pos));int total = len + pos + 2 * LineSep.size();if (total > package.size())return false;*message = package.substr(pos + LineSep.size(), len);package.erase(0, total);return true;
}class Request
{//...
public:// 新增 --- 序列化和反序列化两个函数// 将数据序列化为 "x op y", 其中BlankSep是" "void Serialize(std::string *out){*out = std::to_string(_data_x) + BlankSep + _op + BlankSep + std::to_string(_data_y);}// 将"x op y"反序列化结构化字段, 其中BlankSep是" "bool Deserialize(const std::string &in){auto l = in.find(BlankSep);if (l == std::string::npos)return false;_data_x = std::stoi(in.substr(0, l));auto r = in.rfind(BlankSep);if (r == std::string::npos)return false;_data_y = std::stoi(in.substr(r + BlankSep.size()));if (r - l - (int)BlankSep.size() != 1)return false;_op = in[l + BlankSep.size()];return true;}
};class Response
{//...
public:// 新增 --- 序列化和反序列化两个函数// 将数据转序列化为"result code", 其中BlankSep是" "void Serialize(std::string *out){*out = std::to_string(_result) + BlankSep + std::to_string(_code);}// 将"result code"反序列化为结构化字段, 其中BlankSep是" "bool Deserialize(const std::string &in){auto pos = in.find(BlankSep);if (pos == std::string::npos)return false;_result = std::stoi(in.substr(0, pos));_code = std::stoi(in.substr(pos + BlankSep.size()));return true;}
};

当然这些字符串相关的序列化和反序列化操作过于繁琐,我们可以不用手写,可以直接用一些工具帮助我们完成这一过程,如json,protobuf等。

用json举个例子(注意要下载json相关的第三方库)

class Request
{//...
public:void Serialize(std::string *out){Json::Value root;root["data_x"] = _data_x;root["data_y"] = _data_y;root["op"] = _op;Json::FastWriter writer;*out = writer.write(root);}bool Deserialize(const std::string &in){Json::Value root;Json::Reader reader;bool res = reader.parse(in, root);if (res){_data_x = root["data_x"].asInt();_data_y = root["data_y"].asInt();_op = root["op"].asInt();}return res;}
};class Response
{//...
public:void Serialize(std::string *out){Json::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);}bool Deserialize(const std::string &in){Json::Value root;Json::Reader reader;bool res = reader.parse(in, root);if (res){_result = root["result"].asInt();_code = root["code"].asInt();}return res;}
};

 Json序列化后的表达式如下

三、完整代码(包含对socket的封装)

// Calculate.hpp
#pragma once
#include <iostream>
#include "Protocol.hpp"
namespace Calcu
{enum{Success,DivZero,ModZero,Unknown};class Calculate{public:std::shared_ptr<Protocol::Response> cal(std::shared_ptr<Protocol::Request> req){auto resp = factory.BuiltResponse();switch (req->GetOp()){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(DivZero);}else{resp->SetResult(req->GetX() / req->GetY());}}break;case '%':{if (req->GetY() == 0){resp->SetCode(ModZero);}else{resp->SetResult(req->GetX() % req->GetY());}}break;case '^':resp->SetResult(req->GetX() ^ req->GetY());break;case '|':resp->SetResult(req->GetX() | req->GetY());break;case '&':resp->SetResult(req->GetX() & req->GetY());break;default:resp->SetCode(Unknown);break;}return resp;}private:Protocol::Factory factory;};
}//Protocol.hpp
#pragma once
#include <iostream>
#include <memory>
#include <string>
#include <jsoncpp/json/json.h>// #define SelfDefine 1namespace Protocol
{const std::string BlankSep = " ";const std::string LineSep = "\n";std::string Encode(const std::string &message){return std::to_string(message.size()) + LineSep + message + LineSep;}bool Decode(std::string &package, std::string *message){auto pos = package.find(LineSep);if (pos == std::string::npos)return false;int len = std::stoi(package.substr(0, pos));int total = len + pos + 2 * LineSep.size();if (total > package.size())return false;*message = package.substr(pos + LineSep.size(), len);package.erase(0, total);return true;}class Request{public:Request() : _data_x(0), _data_y(0), _op(0){}Request(int data_x, int data_y, char op): _data_x(data_x), _data_y(data_y), _op(op){}void Serialize(std::string *out){
#ifdef SelfDefine*out = std::to_string(_data_x) + BlankSep + _op + BlankSep + std::to_string(_data_y);
#elseJson::Value root;root["data_x"] = _data_x;root["data_y"] = _data_y;root["op"] = _op;Json::FastWriter writer;*out = writer.write(root);
#endif}bool Deserialize(const std::string &in){
#ifdef SelfDefineauto l = in.find(BlankSep);if (l == std::string::npos)return false;_data_x = std::stoi(in.substr(0, l));auto r = in.rfind(BlankSep);if (r == std::string::npos)return false;_data_y = std::stoi(in.substr(r + BlankSep.size()));if (r - l - (int)BlankSep.size() != 1)return false;_op = in[l + BlankSep.size()];return true;
#elseJson::Value root;Json::Reader reader;bool res = reader.parse(in, root);if (res){_data_x = root["data_x"].asInt();_data_y = root["data_y"].asInt();_op = root["op"].asInt();}return res;
#endif}void Inc(){_data_x++;_data_y++;}void DeBug(){std::cout << "data_x :" << _data_x << std::endl;std::cout << "data_y :" << _data_y << std::endl;std::cout << "op :" << _op << std::endl;}int GetX() { return _data_x; }int GetY() { return _data_y; }char GetOp() { return _op; }private:// 序列化// len\nx op y\nint _data_x;int _data_y;char _op;};class Response{public:Response() : _result(0), _code(0){}Response(int result, int code): _result(result), _code(code){}void Serialize(std::string *out){
#ifdef SelfDefine*out = std::to_string(_result) + BlankSep + std::to_string(_code);
#elseJson::Value root;root["result"] = _result;root["code"] = _code;Json::FastWriter writer;*out = writer.write(root);
#endif}bool Deserialize(const std::string &in){
#ifdef SelfDefineauto pos = in.find(BlankSep);if (pos == std::string::npos)return false;_result = std::stoi(in.substr(0, pos));_code = std::stoi(in.substr(pos + BlankSep.size()));return true;
#elseJson::Value root;Json::Reader reader;bool res = reader.parse(in, root);if (res){_result = root["result"].asInt();_code = root["code"].asInt();}return res;
#endif}void SetResult(int res){_result = res;}void SetCode(int code){_code = code;}int GetResult(){return _result;}int GetCode(){return _code;}private:int _result;int _code;};// 建造类 --- 设计模式class Factory{public:std::shared_ptr<Request> BuiltRequest(){std::shared_ptr<Request> req = std::make_shared<Request>();return req;}std::shared_ptr<Request> BuiltRequest(int data_x, int data_y, char op){std::shared_ptr<Request> req = std::make_shared<Request>(data_x, data_y, op);return req;}std::shared_ptr<Response> BuiltResponse(){std::shared_ptr<Response> resp = std::make_shared<Response>();return resp;}std::shared_ptr<Response> BuiltResponse(int result, int code){std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);return resp;}};
}// 对sockfd进行封装
//对网络套接字进行封装
// Socket.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>#include <string.h>
#include <memory>
#include <string>
#include <unistd.h>const int defaultsockfd = -1;
const int defaultbacklog = 5;
#define CONV(addr) ((struct sockaddr *)addr)
namespace zxws
{enum{SockError = 1,BindError,ListenError,AcceptError,ConnectError};// 模板方法 --- 设计模式的一种// 对网络套接字进行封装 --- 屏蔽内部建立连接的过程class Socket{public:virtual ~Socket() {}virtual void CreateSocket() = 0;virtual void BindSocket(uint16_t port) = 0;virtual void ListenSocketOrDie(int backlog) = 0;virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport) = 0;virtual bool ConnectServer(const std::string &peerip, uint16_t peerport) = 0;virtual int GetSockfd() = 0;virtual void SetSockFd(int sockfd) = 0;virtual void CloseSockfd() = 0;public:void BuildListenSocket(uint16_t port, int backlog){CreateSocket();BindSocket(port);ListenSocketOrDie(backlog);}bool BuildConnectSocket(const std::string &peerip, uint16_t peerport){CreateSocket();return ConnectServer(peerip, peerport);}void BuildNormalSocket(int sockfd){SetSockFd(sockfd);}};class TcpSocket : public Socket{public:TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd){}virtual ~TcpSocket() {}virtual void CreateSocket() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0)exit(SockError);std::cout << "create success , sockfd : " << _sockfd << std::endl;}virtual void BindSocket(uint16_t port) override{struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;int n = bind(_sockfd, CONV(&local), sizeof(local));if (n < 0)exit(BindError);std::cout << "bing success , sockfd: " << _sockfd << std::endl;}virtual void ListenSocketOrDie(int backlog) override{int n = listen(_sockfd, backlog);if (n < 0)exit(ListenError);std::cout << "listen success , sockfd: " << _sockfd << std::endl;}virtual Socket *AcceptConnection(std::string *peerip, uint16_t *peerport){struct sockaddr_in server;socklen_t len = sizeof(server);int sockfd = accept(_sockfd, CONV(&server), &len);if (sockfd < 0)return nullptr;char buffer[INET_ADDRSTRLEN];inet_ntop(AF_INET, &server.sin_addr, buffer, sizeof(buffer));*peerip = buffer;*peerport = ntohs(server.sin_port);return new TcpSocket(sockfd);}virtual bool ConnectServer(const std::string &peerip, uint16_t peerport) override{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(peerport);inet_pton(_sockfd, peerip.c_str(), &server.sin_addr);int n = connect(_sockfd, CONV(&server), sizeof(server));return n == 0;}virtual int GetSockfd(){return _sockfd;}virtual void SetSockFd(int sockfd){_sockfd = sockfd;}void CloseSockfd(){if (_sockfd >= 0)close(_sockfd);}private:int _sockfd;};
}//TcpServer.hpp
#pragma once
#include "Socket.hpp"
#include <pthread.h>
#include <functional>using func_t = std::function<std::string(std::string&,bool*)>;
class TcpServer;class ThreadData
{
public:ThreadData(TcpServer* tcp_this,zxws::Socket* sockp):_this(tcp_this),_sockp(sockp){}
public:TcpServer*_this;zxws::Socket* _sockp;
};class TcpServer
{
public:TcpServer(int port, func_t task): _port(port), _listensockfd(new zxws::TcpSocket()), _task(task){}static void *ThreadRoutine(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);std::string inbuffer;while (1){bool ok = true;// 1、接收if(!td->_sockp->Recv(&inbuffer,1024))break;// 2、处理std::string send_str = td->_this->_task(inbuffer,&ok);if(ok){if(!send_str.empty()){// 3、发送td->_sockp->Send(send_str);}}else{break;}}//注意顺序td->_sockp->CloseSockfd();delete td->_sockp;delete td;return nullptr;}void Loop(){_listensockfd->BuildListenSocket(_port, defaultbacklog);std::string ip;uint16_t port;while (true){zxws::Socket *sockfd = _listensockfd->AcceptConnection(&ip, &port);if (sockfd == nullptr)continue;std::cout << "accept success , [" << ip << ":" << port << "]"<< " sockfd " << sockfd->GetSockfd() << std::endl;// sockfd->CloseSockfd();ThreadData *td = new ThreadData(this,sockfd);pthread_t tid;pthread_create(&tid, nullptr, ThreadRoutine, td);}}~TcpServer(){delete _listensockfd;}private:int _port;zxws::Socket *_listensockfd;func_t _task;
};//TcpServer.cpp
#include "Socket.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Calculate.hpp"
using namespace Protocol;
using namespace zxws;
using namespace Calcu;
std::string Handler(std::string &inbuffer, bool *error_code)
{*error_code = true;std::unique_ptr<Factory> factory(new Factory);auto req = factory->BuiltRequest();Calculate calculate;// 1、分析字节流std::string message;std::string ret;while (Decode(inbuffer, &message)){// 2、反序列化if (!req->Deserialize(message)){*error_code = false;return std::string();}// 3、业务逻辑auto resp = calculate.cal(req);// 4、序列化std::string send_str;resp->Serialize(&send_str);// 5、添加报头send_str = Encode(send_str);ret += send_str;}return ret;
}int main(int argc, char *argv[])
{if (argc != 2){std::cout << "Usage : " << argv[0] << " port" << std::endl;return 0;}uint16_t port = std::stoi(argv[1]);std::unique_ptr<TcpServer> svr(new TcpServer(port, Handler));svr->Loop();return 0;
}//TcpClient.cpp
#include "Socket.hpp"
#include "Protocol.hpp"
using namespace Protocol;
const std::string opers = "+-/*%=&^|";
int main(int argc, char *argv[])
{if (argc != 3){std::cout << "Usage : " << argv[0] << " ip port" << std::endl;return 0;}uint16_t port = std::stoi(argv[2]);std::string ip = argv[1];zxws::Socket *sock = new zxws::TcpSocket();if (!sock->BuildConnectSocket(ip, port)){std::cout << "connect failed" << std::endl;return 0;}std::cout << "connect success" << std::endl;std::unique_ptr<Factory> factory(new Factory);srand(time(nullptr));std::string response;while (1){// 1、创建消息int x = rand() % 100;int y = rand() % 100;char op = opers[rand() % opers.size()];usleep(1000);std::shared_ptr<Request> req = factory->BuiltRequest(x, y, op);req->DeBug();// 2、序列化std::string requires;req->Serialize(&requires);std::cout<<requires<<std::endl;// 3、添加报头std::string res = Encode(requires);// 4、发送sock->Send(res);// 5、接收sock->Recv(&response, 1024);// 6、分析字节流std::string message;if (!Decode(response, &message))break;// 7、反序列化auto resp = factory->BuiltResponse();if (!resp->Deserialize(message))break;// 打印std::cout << x << op << y << " = " << resp->GetResult() << " [" << resp->GetCode() << "]" << std::endl;}sock->CloseSockfd();return 0;
}

四、总结和扩展

当然这只是一个协议,我们以后如果想要增加功能,可以再去定义协议,只要在序列化数据的头部加上该请求想要访问哪个服务的即可,其他操作步骤和我们制定网络计算器一样。

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

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

相关文章

苹果AI终于来了!从2.7到30亿四款大模型代码全开源,AI技术持续“狂飙”|钛媒体AGI

&#xff08;图片来源&#xff1a;Apple官网&#xff09; 苹果公司突然公布了一则大新闻。 北京时间4月25日凌晨&#xff0c;苹果在 Hugging Face 平台上发布一个“具有开源训练和推理框架的高效语言模型”&#xff0c;名为 OpenELM。 据了解&#xff0c;OpenELM有四种尺寸&…

Magnet for Mac:高效窗口管理工具

Magnet for Mac是一款专为Mac用户设计的窗口管理工具&#xff0c;旨在帮助用户更高效地管理和布局多个应用程序窗口&#xff0c;提升工作效率。 Magnet for Mac v2.14.0中文免激活版下载 这款软件拥有直观易用的界面和丰富的功能&#xff0c;支持用户将屏幕分割成多个区域&…

Linux操作系统的安装与配置

目录 (1)实验目的&#xff1a; (2)实验内容&#xff1a; (3)实验原理&#xff1a; (4)实验步骤&#xff1a; 1.先下载vmware workstation pro软件&#xff0c;下载地址:https://www.vmware.com/products/workstation-pr o/workstation-pro-evaluation.html 2.下载完成后&…

vue+element 树形结构 改成懒加载模式(原理element有),这里只做个人理解笔记

1 找到属性标签添加 lazy 和 :load"loadNode" 这两个属性 2 引入树形接口,并和后端约定好传值,(拿我的举例 第一次获取全部父级默认第一次传参数:{ parentId : 0},可获取全部父级 第二次通过点击的子级把子级id传进去,这一步就用到了:load"loadNode&quo…

路由器使用docker安装mysql和redis服务

路由器使用docker安装mysql和redis服务 1.先在路由器中开启docker功能 &#xff08;需要u盘 或者 移动硬盘&#xff09; 2. docker 管理地址 :http://192.168.0.1:11180/#/ 3. 拉取镜像 4. mysql容器参数设置 MYSQL_ROOT_PASSWORD 5. redis 容器设置 开发经常需要用到 &…

Synchronized关键字的深入分析

一、引言 在多线程编程中&#xff0c;正确地管理并发是确保程序正确运行的关键。Java提供了多种同步工具&#xff0c;其中synchronized关键字是最基本且最常用的同步机制之一。本文旨在深入解析synchronized的实现原理&#xff0c;探讨其在不同应用场景中的使用&#xff0c;并…

​解析什么是物联网接入网关?-天拓四方

随着物联网技术的飞速发展&#xff0c;越来越多的设备、传感器和系统被连接到互联网&#xff0c;形成了一个庞大的、相互连接的智能网络。在这个网络中&#xff0c;物联网接入网关扮演着至关重要的角色&#xff0c;它不仅是连接物联网设备和云平台的桥梁&#xff0c;还是实现设…

数据结构-二叉树-堆(二)

一、建堆的时间复杂度问题 1、除了向上调整建堆&#xff0c;我们还可以向下调整建堆。不能在根上直接开始向下调整。这里的条件就是左右子树必须都是大堆或者小堆。我们可以倒着往前走&#xff0c;可以从最后一个叶子开始调整。但是从叶子开始调整没有意义。所以我们可以从倒数…

mysql的多表查询和子查询

多表查询&#xff1a;查询数据时&#xff0c;需要使用多张表来查询 多表查询分类&#xff1a; 1.内连接查询 2.外连接查询 3.子查询 笛卡尔积&#xff1a; create table class (id int primary key auto_increment,name varchar(10) ); create table student (id int primar…

serdes 同轴电缆和双绞线接法

1、同轴电缆 Coaxial Cable 2、双绞线STP&#xff08;Shielded Twisted Pair&#xff09; 比如我们用的车载camera一般就只需要接一路即可&#xff0c;RIN接camera&#xff0c; RIN-通过电容接地。

python基础——正则表达式

&#x1f4dd;前言&#xff1a; 这篇文章主要想讲解一下python中的正则表达式&#xff1a; 1&#xff0c;什么是正则表达式 2&#xff0c;re模块三匹配 3&#xff0c;元字符匹配 4&#xff0c;具体示例 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&am…

认识HTTP

HTTP缺点 通信使用明文&#xff08;不加密&#xff09;&#xff0c;内容可能会被窃听 不验证通信方的身份&#xff0c;可能遭遇伪装 无法证明报文的完整性&#xff0c;所以有可能遭篡改 一、通信使用明文&#xff08;不加密&#xff09;&#xff0c;内容可能会被窃听 TCP/…

Git--基础学习--面向企业--持续更新

一、基础学习 1.1基本命令 //查询基础信息 git config --global --list //选取合适位置创建 mkdir 文件名 //创建文件夹 //全局配置 git config --global user.email "****e***i" git config --global user.name "*** K****"//--------------------进入…

西瓜书学习——线性判别分析

文章目录 定义LDA的具体步骤1. 计算类内散布矩阵&#xff08;Within-Class Scatter Matrix&#xff09;2. 计算类间散布矩阵&#xff08;Between-Class Scatter Matrix&#xff09;3. 求解最佳投影向量4. 数据投影5. 分类 定义 线性判别分析&#xff08;Linear Discriminant A…

php动态高亮web源代码

php动态高亮web源代码 注&#xff1a;配置好不允许高亮的文件名&#xff0c;安全第一 #php实现动态展示目录树结构源代码 适用于开放源代码&#xff0c;结合html缓存使用效果更佳&#xff0c;因循环较多不适合放首页 能力有限没实现行号 演示&#xff1a;show source|开放…

多家企业机密数据遭Lockbit3.0窃取,亚信安全发布《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件87起&#xff0c;与上周相比勒索事件大幅下降。美国依旧为受勒索攻击最严重的国家&#xff0c;占比45%。 本周Cactus是影响最严重的勒索家族&#xff0c;Lockbit3.0和Bianlian恶意家族紧随其后&#xff0c;从整体上看Lockbit3.0依旧…

git 命令怎么回退到指定的某个提交 commit hash 并推送远程分支?

问题 如下图&#xff0c;我要回退到 【002】Babel 的编译流程 这一次提交 解决 1、先执行下面命令&#xff0c;输出日志&#xff0c;主要就是拿到提交 commit 的 hash&#xff0c;上图红框即可 git log或者 vscode 里面直接右击&#xff0c;copy sha 2、执行下面命令回退 g…

05_Scala运算符

文章目录 **1.Scala运算符****2.scala中没有 --等语法****3.逻辑运算符和Java完全相同****4.scala认为万物皆对象** 1.Scala运算符 Scala底层 使用的是equals() 程序员比较两个量的时候&#xff0c;谁来没事比较内存地址&#xff1f; Java中引用数据类型比较地址&#xff0…

黑马点评(十二) -- UV统计

一 . UV统计-HyperLogLog 首先我们搞懂两个概念&#xff1a; UV&#xff1a;全称Unique Visitor&#xff0c;也叫独立访客量&#xff0c;是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站&#xff0c;只记录1次。 PV&#xff1a;全称Page View&…

Games101-动画与模拟(基本概念、质点弹簧系统、运动学)

动画&#xff1a;把物体变成活的&#xff0c;让它动起来 更关注的是美学。早期的动画是画出来的&#xff0c;并不关心对不对&#xff0c;符不符合物理&#xff0c;只要看起来对 图形学里对动画理解为对于建模或几何的拓展。动画无非就是在不同的时间或不同的帧有不同的几何形状…