【Linux网络】应用层自定义协议与序列化及Socket模拟封装

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 🏳️‍🌈一、应用层
    • 1.1 再谈 "协议"
    • 1.2 网络版计算器
    • 1.3 序列化和反序列化
  • 🏳️‍🌈二、什么是全双工
  • 🏳️‍🌈三、Socket封装
    • 3.1 整体结构
    • 3.2 Socket 基类
    • 3.3 TcpSocket 子类
      • 3.3.1 基本结构
      • 3.3.2 构造、析构函数
      • 3.3.3 创建套接字
      • 3.3.4 绑定套接字
      • 3.3.5 监听套接字
      • 3.3.6 获取连接
      • 3.3.7 建立连接
      • 3.3.8 其他方法
      • 3.3.10 整体代码
  • 👥总结


11111111
11111111
11111111
11111111
**** 11111111

🏳️‍🌈一、应用层

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.

1.1 再谈 “协议”

协议是一种 “约定”. socket api 的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些 “结构化的数据” 怎么办呢?

其实,协议就是双方约定好的结构化的数据!

1.2 网络版计算器

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端.

约定方案一(传结构体对象):

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
  • 数字和运算符之间没有空格;

不推荐直接传结构体对象,从技术和业务角度解释?

1、技术角度

  • 1、跨平台与兼容性:

    • 结构体的大小和内存布局可能因编译器、操作系统或硬件平台的不同而有所差异。这可能导致在一个平台上发送的结构体在另一个平台上无法正确解析。
  • 2、内存对齐与填充:

    • 为了优化内存访问速度,编译器可能会对结构体成员进行对齐和填充。这会导致结构体的实际大小大于其成员大小的总和。
    • 直接传输结构体可能会因为内存对齐和填充的问题而导致数据解析错误。
  • 3、指针与动态内存:

    • 结构体中可能包含指针,这些指针指向动态分配的内存。直接传输结构体无法传递指针所指向的数据,而只能传递指针值,这可能导致数据丢失或内存泄漏。

2、业务角度

  • 1、数据安全性:
    • 直接传输结构体可能会暴露数据的内部结构和实现细节,从而增加数据被恶意攻击的风险。
  • 2、数据版本控制:
    • 随着业务的发展和变化,数据结构和格式可能需要进行调整和升级。

约定方案二(传字符串):

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体;
  • 这个过程叫做 “序列化” 和 “反序列化

1.3 序列化和反序列化

在这里插入图片描述

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据,在另一端能够正确的进行解析, 就是 ok 的. 这种约定, 就是 应用层协议

但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。

  • 我们采用方案 2,我们也要体现协议定制的细节
  • 我们要引入序列化和反序列化,只不过我们课堂直接采用现成的方案 – jsoncpp库
  • 我们要对 socket 进行字节流的读取处理

🏳️‍🌈二、什么是全双工

在这里插入图片描述

所以:

  • 在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工
  • 这就是为什么一个 tcp sockfd 读写都是它的原因
  • 实际数据什么时候发,发多少,出错了怎么办,由 TCP 控制,所以 TCP 叫做传输控制协议

1、read,write,send,recv本质是拷贝函数
2、发送数据的本质:是从发送方的发送缓冲区把数据通过协议栈和网络拷贝给接收方大的接收缓冲区!
3、tcp支持全双工通信的原因(有发送和接收缓冲区)!
4、有两个缓冲区这种模式就是生产者消费者模型
5、为什么IO函数要阻塞?本质是在维护同步关系!

TCP协议是面向字节流的,客户端发的,不一定是全部是服务端收的,怎么保证读到的是一个完整的请求呢?

  • 分割完整的报文!

🏳️‍🌈三、Socket封装

Socket类以模板方法类的设计模式进行封装,将算法的不变部分封装在抽象基类中而将可变部分延迟到子类中实现!

3.1 整体结构

  • 抽象类(Abstract Class)

    1. 定义了多个抽象操作,这些操作在抽象类中不具体实现,由子类实现。
    2. 定义了两个模板方法,这个方法通常调用了上面提到的抽象操作。模板方法的算法骨架是固定的,但其中一些步骤的具体实现会延迟到子类中。
  • 具体子类(Concrete Class)

    1. 实现抽象类中的抽象操作,提供具体的算法步骤实现。
    2. 可以重写父类中的模板方法,但通常情况下不需要这么做,因为模板方法已经在抽象类中定义好了算法的骨架。

3.2 Socket 基类

将套接字创建、绑定、监听等 ​通用流程​ 抽象为模板方法,如 BuildListenSocket而将具体步骤(如 CreaterSocketOrDie)延迟到子类实现。

​角色划分​:

  • ​抽象类(Abstract Class)​​:Socket 定义了纯虚函数(步骤方法)和模板方法(流程框架)。​
  • 具体子类(Concrete Class)​​:由用户继承 Socket 并实现纯虚函数(例如 TcpSocket、UdpSocket)。
using SockPtr = std::shared_ptr<Socket>;class Socket {public:virtual void CreateSocketOrDie() = 0;                 // 创建套接字virtual void BindOrDie(uint16_t port) = 0;            // 绑定套接字virtual void ListenOrDie(int backlog = gbacklog) = 0; // 监听套接字virtual SockPtr Accepter(InetAddr* cli) = 0;          // 获取链接virtual bool Connector(const std::string& peerip,uint16_t peerport) = 0; // 简历连接virtual int Sockfd() = 0;virtual void Close() = 0;virtual ssize_t Recv(std::string* out) = 0;      // 接收数据virtual ssize_t Send(const std::string& in) = 0; // 发送数据public:// 创建监听套接字void BuildListenSocket(uint16_t port) {CreateSocketOrDie(); // 创建BindOrDie(port);     // 绑定ListenOrDie();       // 监听}// 创建客户端套接字void BuildConnectorSocket(const std::string& peerip, uint16_t peerport) {CreateSocketOrDie();         // 创建Connector(peerip, peerport); // 连接}
};

3.3 TcpSocket 子类

TcpSocket类 继承 Socket类,并具体实现父类的抽象操作!

3.3.1 基本结构

TcpSocket 子类就是具体实现父类的抽象操作,所以所有 TCP 可能会用到的父类方法都要具体实现

class TcpSocket : public Socket {
public:TcpSocket() {}TcpSocket(int sockfd) {}// 创建套接字void CreateSocketOrDie() {}// 绑定套接字void BindOrDie(uint16_t port) {}// 监听套接字void ListenOrDie(int backlog = gbacklog) {}// 获取链接SockPtr Accepter(InetAddr* cli) {}// 建立连接bool Connector(const std::string& peerip, uint16_t peerport) {}// 获取套接字描述符int Sockfd() {}// 关闭套接字void Close() {}~TcpSocket() {}private:int _sockfd;
};

3.3.2 构造、析构函数

  • 构造函数 可以实现两个,一个无参构造,一个有参构造(传参sockfd),用于初始化成员变量
  • 析构函数 可以不做处理,后面关闭套接字自己调用关闭函数即可!
TcpSocket() {}
TcpSocket(int sockfd) : _sockfd(sockfd) {}
~TcpSocket() {}

3.3.3 创建套接字

使用库函数 socket 按照格式创建套接字

// 创建套接字
void CreateSocketOrDie() override {_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_sockfd < 0) {LOG(LogLevel::ERROR) << "create socket error";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd create success : " << _sockfd;
}

3.3.4 绑定套接字

这部分用于服务端,所以需要先构建服务端的网络字节序地址,然后绑定套接字和网络字节序地址

// 绑定套接字void BindOrDie(uint16_t port) override{// sockaddr_in 的头文件是 #include <netinet/in.h>struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = htonl(INADDR_ANY);int n = ::bind(_sockfd, CONV(&local), sizeof(local));if(n < 0){LOG(LogLevel::ERROR) << "bind socket error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";}

3.3.5 监听套接字

在绑定好网络字节序地址后,我们需要形成老板模式,设置最大连接数量,然后不断监听

// 监听套接字
void ListenOrDie(int backlog = gbacklog) override {int n = ::listen(_sockfd, backlog);if (n < 0) {LOG(LogLevel::ERROR) << "listen socket error";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success";
}

3.3.6 获取连接

在为监听套接字设置好最大连接长度后,我们不断使用 accept 监听这个套接字,将获取到的客户端ip和端口号等信息 与 我们的服务端的连接 整合起来,形成一个整体套接字,实现面向对象连接

// 获取链接
SockPtr Accepter(InetAddr* cli) override {struct sockaddr_in client;socklen_t clientlen = sizeof(client);// accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)// 返回一个新的套接字,该套接字与调用进程间接地建立了连接。int sockfd = ::accept(_sockfd, CONV(&client), &clientlen);if (sockfd < 0) {LOG(LogLevel::ERROR) << "accept socket error";return nullptr;}*cli = InetAddr(client);LOG(LogLevel::DEBUG) << "get a new connection from "<< cli->AddrStr().c_str() << ", sockfd : " << sockfd;return std::make_shared<TcpSocket>(sockfd);
}

3.3.7 建立连接

当客户端想要连接上服务端的时候,我们需要先为服务端创建一个网络字节序地址,再与客户端的套接字连接起来

// 建立连接
bool Connector(const std::string& serverip, uint16_t serverport) override {struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;         // IPv4协议server.sin_port = htons(serverport); // 端口号// 这句话表示将字符串形式的IP地址转换为网络字节序的IP地址// inet_pton函数的作用是将点分十进制的IP地址转换为网络字节序的IP地址// 这里的AF_INET表示IPv4协议// 这里的serverip.c_str()表示IP地址的字符串形式// &server.sin_addr表示将IP地址存储到sin_addr成员变量中::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr); // IP地址int n = ::connect(_sockfd, CONV(&server), sizeof(server));if (n < 0) {LOG(LogLevel::ERROR) << "connect socket error";return false;}LOG(LogLevel::DEBUG) << "connect success";return true;
}

3.3.8 其他方法

还有一些其他的方法,难度不大,就不一一介绍了

// 获取套接字描述符
int Sockfd() override { return _sockfd; }// 关闭套接字
void Close() override {if (_sockfd >= 0)::close(_sockfd);
}// 接收数据
ssize_t Recv(std::string* out) override {char inbuffer[4096];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if (n > 0) {inbuffer[n] = 0;*out = inbuffer;}return n;
}// 发送数据
ssize_t Send(const std::string& in) override {return ::send(_sockfd, in.c_str(), in.size(), 0);
}

3.3.10 整体代码

#pragma once #include <iostream>
#include <cstring>
#include <Socket.h>
#include <memory>#include <netinet/in.h>#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"using namespace LogModule;const int gbacklog = 8;// common.hpp
// #define Die(code)   \
//     do              \
//     {               \
//         exit(code); \
//     } while (0)// #define CONV(v) (struct sockaddr *)(v)// enum 
// {
//     USAGE_ERR = 1,
//     SOCKET_ERR,
//     BIND_ERR,
//     LISTEN_ERR,
//     CONNECTION_ERR
// };namespace SocketModule{using SockPtr = std::shared_ptr<Socket>;class Socket{public:virtual void CreateSocketOrDie() = 0;                                       // 创建套接字virtual void BindOrDie(uint16_t port) = 0;                                  // 绑定套接字virtual void ListenOrDie(int backlog = gbacklog) = 0;                       // 监听套接字virtual SockPtr Accepter(InetAddr* cli) = 0;                                // 获取链接virtual bool Connector(const std::string& serverip, uint16_t serverport) = 0;   // 简历连接virtual int Sockfd() = 0;virtual void Close() = 0;virtual ssize_t Recv(std::string* out) = 0;                                  // 接收数据virtual ssize_t Send(const std::string& in) = 0;                             // 发送数据 public:// 创建监听套接字void BuildListenSocket(uint16_t port){CreateSocketOrDie();        // 创建BindOrDie(port);            // 绑定ListenOrDie();              // 监听}// 创建客户端套接字void BuildConnectorSocket(const std::string& serverip, uint16_t serverport){CreateSocketOrDie();        // 创建Connector(serverip, serverport); // 连接}};class TcpSocket : public Socket{public:TcpSocket(){}TcpSocket(int sockfd) : _sockfd(sockfd){ }// 创建套接字void CreateSocketOrDie() override{_sockfd = socket(AF_INET, SOCK_STREAM, 0);if(_sockfd < 0){LOG(LogLevel::ERROR) << "create socket error";exit(SOCKET_ERR);}LOG(LogLevel::DEBUG) << "sockfd create success : " << _sockfd;}// 绑定套接字void BindOrDie(uint16_t port) override{// sockaddr_in 的头文件是 #include <netinet/in.h>struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = htonl(INADDR_ANY);int n = ::bind(_sockfd, CONV(&local), sizeof(local));if(n < 0){LOG(LogLevel::ERROR) << "bind socket error";exit(BIND_ERR);}LOG(LogLevel::DEBUG) << "bind success";}                                  // 监听套接字void ListenOrDie(int backlog = gbacklog) override{int n = ::listen(_sockfd, backlog);if(n < 0){LOG(LogLevel::ERROR) << "listen socket error";exit(LISTEN_ERR);}LOG(LogLevel::DEBUG) << "listen success";}// 获取链接SockPtr Accepter(InetAddr* cli) override{struct sockaddr_in client;socklen_t clientlen = sizeof(client);// accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)// 返回一个新的套接字,该套接字与调用进程间接地建立了连接。int sockfd = ::accept(_sockfd, CONV(&client), &clientlen);if(sockfd < 0){LOG(LogLevel::ERROR) << "accept socket error";return nullptr;}*cli = InetAddr(client);LOG(LogLevel::DEBUG) << "get a new connection from " << cli->AddrStr().c_str() << ", sockfd : " << sockfd;return std::make_shared<TcpSocket>(sockfd);}// 建立连接bool Connector(const std::string& serverip, uint16_t serverport) override{struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;    // IPv4协议server.sin_port = htons(serverport); // 端口号// 这句话表示将字符串形式的IP地址转换为网络字节序的IP地址// inet_pton函数的作用是将点分十进制的IP地址转换为网络字节序的IP地址// 这里的AF_INET表示IPv4协议// 这里的serverip.c_str()表示IP地址的字符串形式// &server.sin_addr表示将IP地址存储到sin_addr成员变量中::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);   // IP地址int n = ::connect(_sockfd, CONV(&server), sizeof(server));if(n < 0){LOG(LogLevel::ERROR) << "connect socket error" ;return false;}LOG(LogLevel::DEBUG) << "connect success";return true;}// 获取套接字描述符int Sockfd() override{ return _sockfd; }// 关闭套接字void Close() override{ if(_sockfd >= 0) ::close(_sockfd); }// 接收数据ssize_t Recv(std::string* out) override{char inbuffer[4096];ssize_t n = ::recv(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if(n > 0){inbuffer[n] = 0;*out = inbuffer;}return n;}           // 发送数据                        ssize_t Send(const std::string& in) override{return ::send(_sockfd, in.c_str(), in.size(), 0);}~TcpSocket(){}private:int _sockfd;};
}

👥总结

本篇博文对 【Linux网络】应用层自定义协议与序列化及Socket模拟封装 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

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

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

相关文章

基于大模型的结肠癌全病程预测与诊疗方案研究

目录 一、引言 1.1 研究背景与意义 1.2 研究目的与创新点 二、结肠癌概述 2.1 流行病学特征 2.2 发病机制与危险因素 2.3 临床症状与诊断方法 三、大模型技术原理与应用现状 3.1 大模型的基本原理 3.2 在医疗领域的应用情况 3.3 在结肠癌预测中的潜力分析 四、术前…

【UML建模】starUML工具

一.概述 StarUML是一款UML工具&#xff0c;允许用户创建和管理UML&#xff08;统一建模语言&#xff09;模型&#xff0c;广泛应用于软件工程领域。它的主要功能包括创建各种UML图&#xff1a;如用例图、类图、序列图等&#xff0c;支持代码生成与反向工程&#xff0c;以及提供…

模板元编程(Template Metaprogramming, TMP)

C 模板元编程&#xff08;Template Metaprogramming, TMP&#xff09; 模板元编程是一种利用 C 模板系统在 编译期间 完成计算、类型操作和代码生成的编程范式。其核心优势在于通过 零运行时开销 实现高效、类型安全的代码。以下是模板元编程的详细分步解析。 1. 编译时计算 …

Android Build Variants(构建变体)详解

Android Build Variants&#xff08;构建变体&#xff09;是 Android 开发中用于生成不同版本应用程序的一种机制。它允许开发者根据不同的需求&#xff0c;如不同的应用市场、不同的功能模块、不同的环境配置等&#xff0c;从同一个代码库中生成多个不同的 APK。 组成部分 B…

26考研|数学分析:数项级数

数项级数这一章的开始&#xff0c;开启了新的关于“级数”这一新的概念体系的学习进程&#xff0c;此部分共包含四章的内容&#xff0c;分别为数项级数、函数项级数、幂级数以及傅里叶级数。这一章中&#xff0c;首先要掌握级数的相关概念与定义&#xff0c;重难点在于掌握判断…

拥抱健康生活,解锁养生之道

在生活节奏日益加快的当下&#xff0c;健康养生已成为人们关注的焦点。科学的养生方法&#xff0c;能帮助我们增强体质、预防疾病&#xff0c;以更饱满的精神状态拥抱生活。 合理饮食是养生的基石。《黄帝内经》中提到 “五谷为养&#xff0c;五果为助&#xff0c;五畜为益&…

房地产安装工程师简历模板

模板信息 简历范文名称&#xff1a;房地产安装工程师简历模板&#xff0c;所属行业&#xff1a;其他 | 职位&#xff0c;模板编号&#xff1a;XUCP9X 专业的个人简历模板&#xff0c;逻辑清晰&#xff0c;排版简洁美观&#xff0c;让你的个人简历显得更专业&#xff0c;找到好…

HTML5 详细学习笔记

1. HTML5 简介 HTML5 是最新的 HTML 标准&#xff0c;于 2014 年 10 月由 W3C 完成标准制定。它增加了许多新特性&#xff0c;包括语义化标签、多媒体支持、图形效果、离线存储等。 1.1 HTML5 文档基本结构 <!DOCTYPE html> <html lang"zh-CN"> <h…

【网络入侵检测】基于Suricata源码分析NFQ IPS模式实现

【作者主页】只道当时是寻常 【专栏介绍】Suricata入侵检测。专注网络、主机安全,欢迎关注与评论。 1. 概要 👋 本文聚焦于 Suricata 7.0.10 版本源码,深入剖析其 NFQ(Netfilter Queue)模式的实现原理。通过系统性拆解初始化阶段的配置流程、数据包监听机制的构建逻辑,以…

C语言结构体和union内存对齐

在C语言的世界里&#xff0c;结构体&#xff08;struct&#xff09;和联合体&#xff08;union&#xff09;的内存布局一直是困扰许多开发者的难题。当我们定义一个结构体时&#xff0c;编译器会按照特定的规则为每个成员分配内存空间&#xff0c;这些规则被称为内存对齐。看似…

本地部署DeepSeek-R1模型接入PyCharm

以下是DeepSeek-R1本地部署及接入PyCharm的详细步骤指南,整合了视频内容及官方文档核心要点: 一、本地部署DeepSeek-R1模型 1. 安装Ollama框架 ​下载安装包 访问Ollama官网(https://ollama.com/download)Windows用户选择.exe文件,macOS用户选择.dmg包。 ​安装验证 双击…

IEEE综述 | 车道拓扑推理20年演进:从程序化建模到车载传感器

导读 车道拓扑推理对于高精建图和自动驾驶应用至关重要&#xff0c;从早期的程序化建模方法发展到基于车载传感器的方法&#xff0c;但是很少有工作对车道拓扑推理技术进行全面概述。为此&#xff0c;本文系统性地调研了车道拓扑推理技术&#xff0c;同时确定了未来研究的挑战和…

开源模型应用落地-语音合成-MegaTTS3-零样本克隆与多语言生成的突破

一、前言 在人工智能技术飞速发展的今天,文本转语音(TTS)技术正以前所未有的速度改变着人机交互的方式。近日,字节跳动与浙江大学联合推出了一款名为MegaTTS3 的开源TTS模型,再次刷新了行业对高质量语音合成的认知。作为一款轻量化设计的模型,MegaTTS3以仅0.45亿参数 的规…

Python爬虫实战:移动端逆向工具Fiddler经典案例

一、引言 在移动互联网迅猛发展的当下,移动端应用产生了海量的数据。对于开发者而言,获取这些数据对于市场调研、竞品分析、数据挖掘等工作具有重要意义。Fiddler 作为一款功能强大的 Web 调试代理工具,能够有效捕获、分析和修改移动端的网络请求,为开发者深入了解移动端网…

AutoGPT超详细教程

AutoGPT超详细教程 AutoGPT 是一个强大的AI代理管理平台&#xff0c;允许用户通过直观的界面构建、部署和自动化复杂工作流程。其核心是ForgeAgent&#xff0c;它管理代理逻辑、工具集成和任务执行&#xff0c;并通过文件存储抽象层安全访问文件。用户可通过CLI创建代理、运行…

【Python网络爬虫实战指南】从数据采集到反反爬策略

目录 前言技术背景与价值当前技术痛点解决方案概述目标读者说明 一、技术原理剖析核心概念图解核心作用讲解关键技术模块说明技术选型对比 二、实战演示环境配置要求核心代码实现案例1&#xff1a;静态页面抓取&#xff08;电商价格&#xff09;案例2&#xff1a;动态页面抓取&…

矩阵运营的限流问题本质上是平台与创作者之间的流量博弈

矩阵运营的限流问题本质上是平台与创作者之间的流量博弈&#xff0c;要系统性解决这一问题&#xff0c;需从技术规避、内容优化、运营策略三个维度构建防御体系。以下结合平台算法逻辑与实战案例&#xff0c;深度解析限流成因及破解之道&#xff1a; 一、技术层&#xff1a;突…

【分布式理论17】分布式调度3:分布式架构-从中央式调度到共享状态调度

文章目录 一、中央式调度器1. 核心思想2. 工作流程3. 优缺点4. **典型案例&#xff1a;Google Borg** 二、两级调度器1. **核心思想**2. **工作流程**3. 优缺点4. **典型案例&#xff1a;Hadoop YARN** 三、共享状态调度器1. **核心思想**2. **工作流程**3. 优缺点4. **典型案例…

QSPI flash xip模式运行

背景&#xff1a; 在做一个项目&#xff0c;调研p-sram当ram用在cadence qspi接口下是否正常&#xff0c;首先用qspi-flash xip模式验证控制器是否支持flash的xip模式。 一、更改步骤&#xff1a; 1.1首先配置链接脚本 默认链接脚本 OUTPUT_FORMAT("elf32-littlearm&q…

【C++】 —— 笔试刷题day_23

一、 打怪 题目解析 我们现在要去刷毛球怪&#xff0c;我的攻击和血量是h和a、毛球怪的攻击和血量是H和A&#xff1b; 我们和毛球怪的对决是轮流攻击(我们先手)&#xff0c;当血量小于等于0时死亡&#xff1b; 现在我们要求在自己存活的条件下&#xff0c;最多能够杀死几只毛球…