【Linux网络】封装Socket

news2026/5/1 2:49:47
1. 模版方法模式模板方法模式是一种行为型设计模式它定义了一个算法的骨架将某些步骤延迟到子类中实现从而在不改变算法结构的情况下允许子类重新定义特定步骤。核心结构抽象类Abstract Class‍定义算法的框架模板方法并声明若干抽象方法或虚方法供子类实现。模板方法通常被声明为final以防止子类重写算法结构。具体子类Concrete Class‍实现抽象类中定义的抽象方法提供算法步骤的具体实现。C 实现示例以下是一个典型的模板方法模式示例以制作饮料为例代码语言javascriptAI代码解释#include iostream using namespace std; // 抽象类定义饮料制作的模板方法 class DrinkTemplate { public: // 模板方法算法骨架 void makeDrink() { boilWater(); brew(); pourInCup(); addCondiments(); } virtual ~DrinkTemplate() {} protected: void boilWater() { cout 煮水 endl; } void pourInCup() { cout 倒入杯子 endl; } virtual void brew() 0; // 子类实现冲泡步骤 virtual void addCondiments() 0; // 子类实现添加调料 }; // 具体子类茶 class Tea : public DrinkTemplate { protected: void brew() override { cout 泡茶 endl; } void addCondiments() override { cout 加柠檬 endl; } }; // 具体子类咖啡 class Coffee : public DrinkTemplate { protected: void brew() override { cout 冲泡咖啡 endl; } void addCondiments() override { cout 加糖和牛奶 endl; } }; int main() { DrinkTemplate* tea new Tea(); tea-makeDrink(); // 输出煮水、泡茶、倒入杯子、加柠檬 DrinkTemplate* coffee new Coffee(); coffee-makeDrink(); // 输出煮水、冲泡咖啡、倒入杯子、加糖和牛奶 delete tea; delete coffee; return 0; }应用场景固定流程可变实现如文档处理PDF/Word 解析、编译流程、游戏循环等。框架设计父类控制整体逻辑子类扩展细节如 GUI 库、网络库。优点与缺点优点提高代码复用性将公共行为集中在父类。允许子类扩展特定步骤符合开闭原则。缺点子类数量可能过多导致系统复杂。父类修改可能影响所有子类。注意事项模板方法应声明为非虚函数如使用 final 或非虚函数以保持算法结构稳定。抽象方法如 brew()应声明为 protected限制外部直接调用。此模式通过继承和多态实现算法的可变性与稳定性的平衡是 C 中常用的设计模式之一。2. 封装Socket那我们就可以抽象一个Socket的基类将创建套接字等需要的系统调用在基类中设为纯虚函数然后我们可以定义两个模板方法一个UDP的模板方法一个TCP的模板方法需要使用哪个传输层协议的网络服务就在主程序中调用哪个模板方法具体子类UDP或TCP服务端可以实现抽象类中虚函数的具体实现不过UDP相对TCP简单一点所以我们具体子类主要实现TCP服务器2.1 Socket基类基类主要就是所需要的系统调用设为纯虚函数然后定义一个TCP服务端所需要的系统调用的模板方法代码如下代码语言javascriptAI代码解释#pragma once #include Common.hpp #include Log.hpp #include InetAddr.hpp namespace SocketModule { using namespace LogModule; const static int gbacklog 16; // 模板方法模式 class Socket { protected: virtual ~Socket() {} virtual void SocketOrDie() 0; virtual void BindOrDie(uint16_t port) 0; virtual void ListenOrDie(int backlog) 0; virtual std::shared_ptrSocket Accept(InetAddr *client) 0; virtual void Close() 0; virtual int Recv(std::string *out) 0; virtual int Send(const std::string message) 0; virtual int Connect(const std::string server_ip, uint16_t port) 0; public: void BuildTcpSocketMethod(uint16_t port, int backlog gbacklog) { SocketOrDie(); BindOrDie(port); ListenOrDie(backlog); } void BuildTcpClientSocketMethod() { SocketOrDie(); } }; }我们基类将TCP服务端需要的系统调用都设为虚函数在前面的文章中我们已经写过TCP网络编程对于需要的系统调用我们已经熟悉了。两个模板方法分别为服务端和客户端调用服务端通过子类TcpSocket多态调用基类中的模板方法来完成创建套接字绑定监听等连接操作2.2 TcpSocket子类这里我们设置两个构造函数一个无参构造用于初始化listen套接字一个用于将connect返回的文件描述符构造为套接字类型而不是直接返回一个int类型的文件描述符这样做的好处是在后续使用该文件描述符时可以直接通过套接字来调用封装的函数而如果是int类型的话只能使用原始的系统调用但我们已经封装了就尽量使用封装的系统调用这样虽然也行但是有点挫代码语言javascriptAI代码解释namespace SocketModule { using namespace LogModule; const static int gbacklog 16; // 模板方法模式 class Socket { protected: virtual ~Socket() {} virtual void SocketOrDie() 0; virtual void BindOrDie(uint16_t port) 0; virtual void ListenOrDie(int backlog) 0; virtual std::shared_ptrSocket Accept(InetAddr *client) 0; virtual void Close() 0; virtual int Recv(std::string *out) 0; virtual int Send(const std::string message) 0; virtual int Connect(const std::string server_ip, uint16_t port) 0; public: void BuildTcpSocketMethod(uint16_t port, int backlog gbacklog) { SocketOrDie(); BindOrDie(port); ListenOrDie(backlog); } void BuildTcpClientSocketMethod() { SocketOrDie(); } }; const static int defaultfd -1; class TcpSocket : public Socket { public: TcpSocket() // 无参构造listensockfd :_sockfd(defaultfd) {} // 将connect返回的文件描述符构造为套接字类型 TcpSocket(int fd) :_sockfd(fd) {} ~TcpSocket() {} private: int _sockfd; // listensockfd, sockfd都可能 }; }对于创建绑定监听这三个必要的基本操作我们已经熟悉了不多说代码如下代码语言javascriptAI代码解释void SocketOrDie() override { _sockfd ::socket(AF_INET, SOCK_STREAM, 0); if(_sockfd 0) { LOG(LogLevel::FATAL) socket error; exit(SOCKET_ERR); } LOG(LogLevel::INFO) socket success; } void BindOrDie(uint16_t port) override { InetAddr local(port); int n ::bind(_sockfd, local.NetAddrPtr(), local.NetAddrLen()); if(n 0) { LOG(LogLevel::FATAL) bind error; exit(BIND_ERR); } LOG(LogLevel::INFO) bind success; } void ListenOrDie(int backlog) override { int n ::listen(_sockfd, backlog); if (n 0) { LOG(LogLevel::FATAL) listen error; exit(LISTEN_ERR); } LOG(LogLevel::INFO) listen success; }基本操作做完接下来就是服务端接受连接了下面我们就来实现Accept代码语言javascriptAI代码解释std::shared_ptrSocket Accept(InetAddr *client) override { struct sockaddr_in peer; socklen_t len sizeof(peer); int fd ::accept(_sockfd, (struct sockaddr*)peer, len); if (fd 0) { LOG(LogLevel::WARNING) accept warning ...; return nullptr; } client-SetAddr(peer); return std::make_sharedTcpSocket(fd); }注意我们这里只是实现虚函数将来是要在外部来调用但是我们需要知道是哪个客户端发送的信息可我们在定义时又不需要用到客户端的地址信息那我们就可以通过输出型参数将地址信息让外部可以拿到。不过我们是从网络中拿到的客户端地址信息所以就需要从网络字节序转为主机字节序那这步我们就可以在定义的时候来做。但是我们封装的InetAddr类只有构造的时候是将网络字节序转为主机字节序我们这里是输出型参数所以我们可以在InetAddr类中新增一个网络转主机的函数SetAddr通过参数来调用SetAddr我们在退出的时候最好还是需要将文件描述符关闭我们之前没有说这个这里提一下代码语言javascriptAI代码解释void Close() override { if (_sockfd 0) ::close(_sockfd); }然后就是读写数据tcp是面向字节流的所以我们上篇文章中选择使用read/write来读写数据这次我们介绍另一种tcp读写数据的系统调用recv系统调用recv 系统调用用于从一个已连接的面向连接的套接字如 SOCK_STREAM即 TCP 套接字或已绑定的无连接套接字如 SOCK_DGRAM即 UDP 套接字接收数据。它类似于 read 系统调用但提供了额外的 flags 参数来控制接收行为。代码语言javascriptAI代码解释#include sys/socket.h ssize_t recv(int sockfd, void *buf, size_t len, int flags);参数详解int sockfd描述 这是一个由 socket() 创建并经过 connect()对于客户端或 accept()对于服务器端处理后的套接字文件描述符。要求 套接字必须是已连接的对于 TCP或已绑定的对于 UDP。void *buf描述 这是一个指向内存缓冲区的指针用于存放接收到的数据。要求 应用程序必须确保这个缓冲区有足够的空间至少 len 字节来存放数据否则会导致内存越界引发未定义行为如程序崩溃。size_t len描述 指定缓冲区 buf 的最大容量即你希望一次最多接收多少字节的数据。注意 recv 最多只会向你返回 len 字节的数据即使对端发送了更多的数据。多余的数据会留在内核的套接字接收缓冲区中等待下一次 recv 调用。int flags描述 这是一个控制接收行为的标志位。它可以是一个或多个标志的按位或OR组合最常用的标志是 0表示默认行为阻塞等待。常用标志0 标准模式。调用将阻塞直到有数据可用或连接关闭。MSG_DONTWAIT 以非阻塞方式操作。如果没有数据立即可用recv 会立即返回失败并设置错误码 EAGAIN 或 EWOULDBLOCK。这是实现高并发网络编程如使用 epoll的关键。MSG_PEEK 窥探数据。将数据从内核缓冲区复制到应用缓冲区 buf但不会将这些数据从内核缓冲区中移除。下一次调用 recv不带 PEEK还会看到这些相同的数据。MSG_WAITALL 阻塞等待直到请求的完整数据len 字节全部到达、发生错误或连接关闭为止。但在某些情况下如收到信号或连接被对端部分关闭它返回的字节数可能仍少于请求的字节数。返回值recv 的返回值是理解其行为的关键成功时 0 返回实际接收到的字节数。这个值可以小于你请求的 len。对于面向流的协议如 TCP这是非常正常的。失败时-1 发生错误并设置全局变量 errno 以指示具体的错误类型。连接关闭时0 这表示对端已经优雅地关闭了连接对于 TCP 来说就是收到了 FIN 包。这是一个正常的关闭信号不应被视为错误。返回 0 是判断对端是否已关闭连接的标准方法。代码如下代码语言javascriptAI代码解释int Recv(std::string *out) override { char buffer[1024]; ssize_t n ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0); if (n 0) { buffer[n] 0; *out buffer; // 特意 } return n; }注意recv(sockfd, buf, len, 0) 基本等价于 read(sockfd, buf, len)。recv 只是多了一个 flags 参数。recvfrom() 是 recv 的增强版主要用于无连接套接字如 UDP。它比 recv 多两个参数可以获取发送方的地址信息。这里我们同样也是需要从外部调用如果外部要得到读取的缓冲区内容就需要通过输出型参数而且输出型参数需要buffer因为外部上层可能还没有将之前的数据拿完那这个时候就不能直接覆盖掉上次的数据所以特意buffersend系统调用send 系统调用用于向一个已连接的套接字如 TCP 套接字发送数据。它类似于 write 系统调用但提供了额外的 flags 参数来控制发送行为。注意对于无连接的套接字如 UDP通常使用 sendto 或 sendmsg因为它们允许指定目标地址。代码语言javascriptAI代码解释#include sys/socket.h ssize_t send(int sockfd, const void *buf, size_t len, int flags);参数详解i**nt sockfd**描述 这是一个由 socket() 创建并经过 connect()对于客户端或 accept()对于服务器端处理后的套接字文件描述符。要求 套接字必须是已连接的对于 TCP或已绑定的对于 UDP但通常用 sendto。const void *buf描述 这是一个指向内存缓冲区的指针该缓冲区包含要发送的数据。要求 应用程序必须确保这个缓冲区包含有效的数据并且至少有 len 字节。size_t len描述 指定要发送数据的字节数。int flags描述 这是一个控制发送行为的标志位。它可以是一个或多个标志的按位或OR组合最常用的标志是 0表示默认行为。常用标志0 标准模式。阻塞发送直到所有数据被内核接管但不一定被对端接收。MSG_DONTWAIT 以非阻塞方式操作。如果数据不能立即被发送比如套接字发送缓冲区已满send 会立即返回失败并设置错误码 EAGAIN 或 EWOULDBLOCK。这是实现高并发网络编程的关键。MSG_OOB 发送带外数据Out-of-band data。这用于发送紧急数据但通常不推荐使用因为不同的实现可能不一致。MSG_MORE 提示内核还有更多数据要发送。对于 TCP这个标志会导致内核将数据缓存起来等待后续没有 MSG_MORE 标志的 send 调用时再一起发送。这有助于减少小数据包的传输类似于 TCP_CORK 或 TCP_NODELAY 的调整。返回值send 的返回值是理解其行为的关键成功时 0 返回实际发送的字节数。这个值可以小于你请求的 len特别是在非阻塞模式下。失败时-1 发生错误并设置全局变量 errno 以指示具体的错误类型。代码如下代码语言javascriptAI代码解释int Send(const std::string message) override { return send(_sockfd, message.c_str(), message.size(), 0); }这里我们不做过多介绍多路转接时会详细介绍接下来就是客户端发起连接Connect代码语言javascriptAI代码解释int Connect(const std::string server_ip, uint16_t port) override { InetAddr server(server_ip, port); return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen()); }3. 服务端封装好之后就是使用封装的Socket来实现服务端我们已经实现过了这里就不再介绍了只需要将原先的原生系统调用换成封装的Socket即可代码语言javascriptAI代码解释#pragma once #include Socket.hpp #include memory #include sys/wait.h using namespace SocketModule; using namespace LogModule; using ioservice_t std::functionvoid(std::shared_ptrSocket, InetAddr); class TcpServer { public: TcpServer(uint16_t port, ioservice_t service) :_port(port), _listensockptr(std::make_uniqueTcpSocket()), _isrunning(false), _service(service) { _listensockptr-BuildTcpSocketMethod(_port); } void Start() { _isrunning true; while (_isrunning) { InetAddr client; auto sock _listensockptr-Accept(client); // 1. 和client通信sockfd 2. client 网络地址 if (sock nullptr) { continue; } LOG(LogLevel::DEBUG) accept success ... client.StringAddr(); pid_t id fork(); if (id 0) { LOG(LogLevel::FATAL) fork error ...; exit(FORK_ERR); } else if (id 0) { // 子进程 - listensock _listensockptr-Close(); if (fork() 0) exit(OK); // 孙子进程在执行任务已经是孤儿了 _service(sock, client); sock-Close(); exit(OK); } else { // 父进程 - sock sock-Close(); pid_t rid ::waitpid(id, nullptr, 0); (void)rid; } } _isrunning false; } ~TcpServer() {} private: uint16_t _port; std::unique_ptrTcpSocket _listensockptr; bool _isrunning; ioservice_t _service; };我们这里使用多进程分别接收连接和执行任务这里任务我们需要在上层去实现后面文章会详细介绍。后面文章我们会再谈协议然后自己来定义协议然后顶层封装一个任务通过我们自己定义的协议来完成序列化和反序列化让对端拿到我们的任务去处理所以客户端也放在后面实现

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

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

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…