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类:
- TCP监听套接字:显式绑定地址,用于TCP服务端监听来自客户端的连接请求。
- TCP连接套接字:TCP监听套接字accept成功之后返回的用于为客户端提供服务的套接字。
- TCP客户端套接字:隐式绑定地址,通过connect与服务端建立连接。
- UDP服务端套接字:显式绑定地址,用于和客户端进行报文交流。
- 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也是给出了中肯的评价: