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

Linux笔记---封装套接字

1. 模板方法模式

模板方法模式(Template Method Pattern)是一种行为型设计模式,其核心思想是:在抽象类中定义一个算法的骨架(模板方法),将算法中某些可变的步骤延迟到子类中实现,从而让子类在不改变算法整体结构的前提下,灵活定制算法的特定步骤。

模板方法模式包含以下关键角色

  • 抽象类: 定义算法的骨架(模板方法),并声明一些抽象方法(留给子类实现)和具体方法(算法中固定不变的步骤)。 模板方法通常会调用这些抽象方法和具体方法,完成整个算法流程。
  • 具体子类: 继承抽象类,实现抽象类中声明的抽象方法,从而定制算法中的可变步骤。 子类不会修改模板方法本身(即算法的整体结构保持不变)。

以 “冲饮料” 为例,冲咖啡冲茶的流程相似(烧水→冲泡→倒杯→加调料),但 “冲泡” 和 “加调料” 的具体步骤不同,适合用模板方法模式实现:

#include <iostream>
#include <string>// 抽象类:定义算法骨架
class Beverage {
public:// 模板方法:定义制作饮料的整体流程(用final防止子类重写)virtual void prepareRecipe() const final {boilWater();      // 固定步骤:烧水brew();           // 可变步骤:冲泡(子类实现)pourInCup();      // 固定步骤:倒入杯子if (customerWantsCondiments()) {  // 钩子方法:可选步骤addCondiments();  // 可变步骤:加调料(子类实现)}}protected:// 纯虚函数:必须由子类实现的步骤virtual void brew() const = 0;virtual void addCondiments() const = 0;// 具体方法:固定不变的步骤void boilWater() const {std::cout << "烧开水" << std::endl;}void pourInCup() const {std::cout << "倒入杯子中" << std::endl;}// 钩子方法(Hook):默认实现,子类可选择重写virtual bool customerWantsCondiments() const {return true;  // 默认加调料}
};// 具体子类:咖啡
class Coffee : public Beverage {
protected:void brew() const override {std::cout << "用沸水冲泡咖啡粉" << std::endl;}void addCondiments() const override {std::cout << "加牛奶和糖" << std::endl;}// 重写钩子方法:询问是否加调料bool customerWantsCondiments() const override {std::string answer;std::cout << "请问需要加牛奶和糖吗?(y/n): ";std::cin >> answer;return answer == "y" || answer == "Y";}
};// 具体子类:茶
class Tea : public Beverage {
protected:void brew() const override {std::cout << "用沸水浸泡茶叶" << std::endl;}void addCondiments() const override {std::cout << "加柠檬" << std::endl;}
};// 测试
int main() {Beverage* coffee = new Coffee();std::cout << "=== 制作咖啡 ===" << std::endl;coffee->prepareRecipe();Beverage* tea = new Tea();std::cout << "\n=== 制作茶 ===" << std::endl;tea->prepareRecipe();delete coffee;delete tea;return 0;
}

特点

  • 算法骨架固定:模板方法(如prepareRecipe)定义了算法的整体流程,子类无法修改(通常用final修饰)。
  • 可变步骤延迟:抽象方法(如brew、addCondiments)由子类实现,灵活定制具体细节。
  • 代码复用:抽象类中封装了公共步骤(如boilWater),避免子类重复实现。

优点

  • 封装不变部分,扩展可变部分,符合 “开闭原则”
  • 提取公共代码,减少重复,便于维护
  • 父类控制算法流程,子类专注实现细节,职责清晰

缺点

  • 每个具体实现都需要一个子类,可能导致类数量增多
  • 子类执行的结果会影响父类,一定程度上破坏了 “里氏替换原则” 的纯粹性。

适用场景

  • 多个子类有相同的算法流程,但部分步骤实现不同(如框架中的生命周期方法)。
  • 需要控制子类扩展时(只允许子类修改特定步骤,不允许改变整体流程)。
  • 希望提取多个类的公共行为,集中到抽象类中。

2. 封装Socket类

AI告诉我,socket类的封装是模板方法模式的典型应用场景,但是我觉得不是很合理,原因如下。

根据我们之前编程的经验,我们可以按照套接字创建的流程和用法将套接字分为5类:

  1. TCP监听套接字:显式绑定地址,用于TCP服务端监听来自客户端的连接请求。
  2. TCP连接套接字:TCP监听套接字accept成功之后返回的用于为客户端提供服务的套接字。
  3. TCP客户端套接字:隐式绑定地址,通过connect与服务端建立连接。
  4. UDP服务端套接字:显式绑定地址,用于和客户端进行报文交流。
  5. UDP客户端套接字:隐式绑定地址,用于和服务端进行报文交流。

但是,显然这些套接字无论是创建的过程还是用法都大相径庭,很难说有什么固定的流程。

但是但是,AI都这么说了,我还是试着来封装一下。我希望封装之后的成果就是:各种套接字被被创建出来就直接完成了初始化,直接开始发挥自己主要的作用

例如,TCP监听套接字被创建出来之后就可以开始调用自己的Accept方法等待连接,TCP服务端套接字、UDP服务端/客户端套接字被创建出来就可以开始不断地收发消息。

为了贴合主题,我们先将套接字初始化的流程固定一下:

// 模板方法:固定初始化流程(禁止子类重写)
virtual void Initialize() final
{CreateSocket(); // 创建套接字Bind(); // 绑定地址Listen(); // 设置监听Connect(); // 建立连接
}

这时候就有人问了:bind是每个套接字都要做的吗?listen是每个套接字都要做的吗?connect是每个套接字都要做的吗?

你说的很对,所以我们在实现子类的时候,不需要这些步骤的子类就提供对应方法的空实现即可。

做起来最麻烦的还是接口的设计,例如基类Read\Write接口的设计,如何设计参数与返回值能使得TCP套接字和UDP套接字都能通用。

总之也是非常麻烦,博主也懒得介绍具体的代码了,反正套接字编程的流程大家都很熟悉了,不知道如何设计接口的小伙伴可以参考一下博主的代码:

#pragma once
#include "Common.hpp"
#include "Mutex.hpp"namespace SocketModule
{const int default_pending_num = 10;class TCPConnectSocket;class Socket{protected:int _sockfd;InetAddr _addr;MutexModule::Mutex _mutex;  // 线程安全锁// 基础套接字创建(供子类调用)void CreateSocket(int type){_sockfd = ::socket(AF_INET, type, 0);if(_sockfd == -1){LOG(LogLevel::FATAL) << "socket: 申请套接字失败! " << strerror(errno);throw std::runtime_error("socket create failed");  }}// 模板方法的核心步骤(纯虚函数,由子类实现)virtual void CreateSocket() = 0;virtual void Bind() = 0;virtual void Listen() = 0;virtual void Connect() = 0;// 模板方法:固定初始化流程(禁止子类重写)virtual void Initialize() final{CreateSocket();Bind();Listen();Connect();}public:// 用于服务端/客户端套接字(未连接状态)Socket(const std::string& ip, in_port_t port) : _sockfd(-1), _addr(ip, port){}// 用于已连接套接字(如accept返回的TCP连接)Socket(const int sockfd, const InetAddr& addr): _sockfd(sockfd), _addr(addr){}void Close(){if(_sockfd != -1)::close(_sockfd);_sockfd = -1;}// 虚析构函数确保子类资源正确释放virtual ~Socket(){Close();}const InetAddr& addr() const { return _addr; }// 纯虚函数:定义子类必须实现的接口virtual std::shared_ptr<TCPConnectSocket> Accept() = 0;// TCP协议不考虑第二个参数virtual int Receive(std::string& recv_msg, InetAddr* peer = nullptr) = 0;virtual int Send(const std::string& send_msg, const InetAddr& peer = InetAddr()) = 0;};// TCP连接套接字(由accept返回)class TCPConnectSocket : public Socket{public:TCPConnectSocket(int sockfd, const InetAddr& addr): Socket(sockfd, addr)  // 直接使用已创建的套接字{Initialize();}// 重写基类方法(已连接套接字无需重新创建)void CreateSocket() override {}int Receive(std::string& recv_msg, InetAddr* peer = nullptr) override{MutexModule::LockGuard lock(_mutex);char buffer[BUFFER_SIZE];ssize_t size = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);if (size <= 0){recv_msg.clear();if (size < 0)LOG(LogLevel::ERROR) << "recv error: " << strerror(errno);return size;}buffer[size] = '\0';recv_msg = buffer;return size;}int Send(const std::string& send_msg, const InetAddr& peer = InetAddr()) override{MutexModule::LockGuard lock(_mutex);ssize_t size = ::send(_sockfd, send_msg.c_str(), send_msg.size(), 0);if (size == -1)LOG(LogLevel::ERROR) << "send error: " << strerror(errno);return size;}protected:  // 修正访问权限void Bind() override {}  // 已连接套接字无需绑定void Listen() override {}  // 已连接套接字无需监听void Connect() override {}  // 已连接套接字无需再次连接std::shared_ptr<TCPConnectSocket> Accept() override{return nullptr;  // 连接套接字不处理accept}};// TCP监听套接字class TCPListenSocket : public Socket{public:TCPListenSocket(in_port_t port): Socket("0.0.0.0", port)  // 监听所有网卡{Initialize();}// 重写基类方法(显式标记override)void CreateSocket() override{Socket::CreateSocket(SOCK_STREAM);  // TCP类型}std::shared_ptr<TCPConnectSocket> Accept() override{MutexModule::LockGuard lock(_mutex);  // 线程安全保护InetAddr client;int sockfd = ::accept(_sockfd, client.NetAddrPtr(), &client.AddrLen());client = InetAddr(client.NetAddr());if(sockfd == -1){LOG(LogLevel::WARNING) << "accept: 建立连接失败! " << strerror(errno);return nullptr;}LOG(LogLevel::INFO) << "accept: 建立连接成功! ";return std::make_shared<TCPConnectSocket>(sockfd, client);}protected: void Bind() override{int n = ::bind(_sockfd, _addr.NetAddrPtr(), _addr.AddrLen());if(n == -1){LOG(LogLevel::FATAL) << "bind: 绑定地址信息失败! " << strerror(errno);throw std::runtime_error("bind failed");}}void Listen() override{int n = ::listen(_sockfd, default_pending_num);if(n == -1){LOG(LogLevel::FATAL) << "listen: 设置监听套接字失败! " << strerror(errno);throw std::runtime_error("listen failed");}}void Connect() override  // TCP监听套接字无需主动连接{}int Receive(std::string& recv_msg, InetAddr* peer = nullptr) override{return -1;  // 监听套接字不处理接收}int Send(const std::string& send_msg, const InetAddr& peer = InetAddr()) override{return -1;  // 监听套接字不处理发送}};// TCP客户端套接字class TCPClientSocket : public Socket{public:TCPClientSocket(const std::string& ip, in_port_t port): Socket(ip, port){Initialize();}void CreateSocket() override{Socket::CreateSocket(SOCK_STREAM);  // TCP类型}void Connect() override{int n = ::connect(_sockfd, _addr.NetAddrPtr(), _addr.AddrLen());if(n == -1){LOG(LogLevel::FATAL) << "connect: 连接失败! " << strerror(errno);throw std::runtime_error("connect failed");}LOG(LogLevel::INFO) << "客户端套接字已连接到[" << _addr.Info() << "]";}int Receive(std::string& recv_msg, InetAddr* peer = nullptr) override{MutexModule::LockGuard lock(_mutex);char buffer[BUFFER_SIZE];ssize_t size = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);if (size <= 0){recv_msg.clear();if (size < 0)LOG(LogLevel::ERROR) << "recv error: " << strerror(errno);return size;}buffer[size] = '\0';recv_msg = buffer;return size;}int Send(const std::string& send_msg, const InetAddr& peer = InetAddr()) override{MutexModule::LockGuard lock(_mutex);ssize_t size = ::send(_sockfd, send_msg.c_str(), send_msg.size(), 0);if (size == -1)LOG(LogLevel::ERROR) << "send error: " << strerror(errno);return size;}protected:  // 修正访问权限void Bind() override {}  // 客户端通常不主动绑定void Listen() override {}  // 客户端无需监听std::shared_ptr<TCPConnectSocket> Accept() override{return nullptr;  // 客户端不处理accept}};// UDP服务端套接字class UDPServerSocket : public Socket{public:UDPServerSocket(const std::string& ip, in_port_t port): Socket(ip, port){Initialize();}void CreateSocket() override{Socket::CreateSocket(SOCK_DGRAM);  // UDP类型}int Receive(std::string& recv_msg, InetAddr* peer = nullptr) override{MutexModule::LockGuard lock(_mutex);char buffer[BUFFER_SIZE];ssize_t size = ::recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, peer->NetAddrPtr(), &peer->AddrLen());*peer = InetAddr(peer->NetAddr());if (size <= 0){recv_msg.clear();if (size < 0)LOG(LogLevel::ERROR) << "recvfrom error: " << strerror(errno);return size;}buffer[size] = '\0';recv_msg = buffer;return size;}int Send(const std::string& send_msg, const InetAddr& peer = InetAddr()) override{MutexModule::LockGuard lock(_mutex);if (peer.Ip().empty())  // 确保目标地址有效{LOG(LogLevel::ERROR) << "sendto error: 目标地址为空";return -1;}ssize_t size = ::sendto(_sockfd, send_msg.c_str(), send_msg.size(), 0,peer.NetAddrPtr(), peer.AddrLen());if (size == -1)LOG(LogLevel::ERROR) << "sendto error: " << strerror(errno);return size;}protected:  // 修正访问权限并添加overridevoid Bind() override{int n = ::bind(_sockfd, _addr.NetAddrPtr(), _addr.AddrLen());if(n == -1){LOG(LogLevel::FATAL) << "bind: 绑定地址信息失败! " << strerror(errno);throw std::runtime_error("bind failed");}}void Listen() override {}  // UDP无需监听void Connect() override {}  // UDP服务端无需主动连接std::shared_ptr<TCPConnectSocket> Accept() override{return nullptr;  // UDP不支持accept}};// UDP客户端套接字class UDPClientSocket : public Socket{public:UDPClientSocket(const std::string& ip, in_port_t port): Socket(ip, port){Initialize();}void CreateSocket() override{Socket::CreateSocket(SOCK_DGRAM);  // UDP类型}int Receive(std::string& recv_msg, InetAddr* peer = nullptr) override{MutexModule::LockGuard lock(_mutex);char buffer[BUFFER_SIZE];ssize_t size = ::recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, peer->NetAddrPtr(), &peer->AddrLen());*peer = InetAddr(peer->NetAddr());if (size <= 0){recv_msg.clear();if (size < 0)LOG(LogLevel::ERROR) << "recvfrom error: " << strerror(errno);return size;}buffer[size] = '\0';recv_msg = buffer;return size;}int Send(const std::string& send_msg, const InetAddr& peer = InetAddr()) override{MutexModule::LockGuard lock(_mutex);InetAddr target = peer.Ip().empty() ? _addr : peer;  // 支持默认目标地址ssize_t size = ::sendto(_sockfd, send_msg.c_str(), send_msg.size(), 0,target.NetAddrPtr(), target.AddrLen());if (size == -1)LOG(LogLevel::ERROR) << "sendto error: " << strerror(errno);return size;}protected:  // 修正访问权限并添加overridevoid Bind() override {}  // UDP客户端通常不绑定void Listen() override {}  // UDP无需监听void Connect() override {}  // UDP无需连接std::shared_ptr<TCPConnectSocket> Accept() override{return nullptr;  // UDP不支持accept}};
}

deepseek也是给出了中肯的评价:


文章转载自:

http://0b4kbeXl.tgfjm.cn
http://IC7FAhum.tgfjm.cn
http://417QWyKW.tgfjm.cn
http://pK8kYfIS.tgfjm.cn
http://Hw2U6sOT.tgfjm.cn
http://0pRabcJY.tgfjm.cn
http://qmXAkzx7.tgfjm.cn
http://dJjZJkap.tgfjm.cn
http://SSmb0yYB.tgfjm.cn
http://ZFlhJ2BF.tgfjm.cn
http://BjfuMxlo.tgfjm.cn
http://Hr3DiF8Z.tgfjm.cn
http://grvKzmsl.tgfjm.cn
http://xGAgmuN6.tgfjm.cn
http://tLTZ1ubf.tgfjm.cn
http://iqUTQSuv.tgfjm.cn
http://rR1J3jrC.tgfjm.cn
http://BaG1I5Nb.tgfjm.cn
http://Oi4Bf4K8.tgfjm.cn
http://MVrr6L2w.tgfjm.cn
http://HkFmqkrH.tgfjm.cn
http://6C00Ap9C.tgfjm.cn
http://bsoKT8Ml.tgfjm.cn
http://uqZmxA6c.tgfjm.cn
http://ygYixfr7.tgfjm.cn
http://mVUfYqhb.tgfjm.cn
http://pB9wo7yO.tgfjm.cn
http://TR5QRZCH.tgfjm.cn
http://PLtKL6Ao.tgfjm.cn
http://5K6vsssQ.tgfjm.cn
http://www.dtcms.com/a/371594.html

相关文章:

  • 轻松Linux-8.动静态库的制作及原理
  • LeetCode 面试经典 150 题:移除元素(双指针思想优化解法详解)
  • 【TypeScript】闭包
  • 后端(fastAPI)学习笔记(CLASS 1):扩展基础
  • Spring Boot @RestController 注解详解
  • 腾讯云语音接口实现会议系统
  • ESP32与SUI-101A实现用电器识别
  • Wan2.2-S2V - 音频驱动图像生成电影级质量的数字人视频 ComfyUI工作流 支持50系显卡 一键整合包下载
  • 开始 ComfyUI 的 AI 绘图之旅-图生图(二)
  • VS2017安装Qt插件
  • ZYNQ FLASH读写
  • 容器元素的滚动条回到顶部
  • 【音频字幕】构建一个离线视频字幕生成系统:使用 WhisperX 和 Faster-Whisper 的 Python 实现
  • ncnn-Android-mediapipe_hand 踩坑部署实录
  • java面试中经常会问到的mysql问题有哪些(基础版)
  • SoundSource for Mac 音频控制工具
  • Unity学习----【进阶】Input System学习(一)--导入与基础的设备调用API
  • 第11篇:降维算法:PCA、t-SNE、UMAP
  • 【Leetcode100】算法模板之二叉树
  • 深入理解假设检验:从抛硬币到药物实验的全景讲解
  • JavaScript笔记之JS 和 HTML5 的关系
  • 第4篇 conda install pytorch==2.0.0报错
  • 基于Echarts+HTML5可视化数据大屏展示-学生综合成绩评价系统大屏
  • 探索OpenResty:高性能Web开发利器
  • Lua 核心知识点详解
  • 26考研——内存管理_内存管理策略(3)
  • MySQL索引和B+Tree的关系
  • 《云原生配置危机:从服务瘫痪到韧性重建的实战全解》
  • 论文阅读-SelectiveStereo
  • 架构思维:重温限流算法原理与实战