使用模板方法设计模式封装 socket 套接字并实现Tcp服务器和客户端 简单工厂模式设计

news2024/11/26 0:47:41

文章目录

  • 使用模板方法设计模式封装套接字
  • 使用封装后的套接字实现Tcp服务器和客户端
    • 实现Tcp服务器
    • 实现Tcp客户端
  • 工厂模式

在这里插入图片描述

使用模板方法设计模式封装套接字

可以使用模块方法设计模式来设计套接字 socket 的封装

模板方法(Template Method)设计模式是一种行为设计模式,它在一个方法中定义了一个算法的骨架,并允许子类为一个或多个步骤提供实现。模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

  1. 抽象类(Abstract Class):定义了一个或多个抽象操作,以及一个模板方法。这个模板方法调用了一个或多个抽象操作。
  2. 具体子类(Concrete Subclass):实现抽象类中的抽象操作,从而完成算法中特定步骤的具体实现。

抽象类定义了一个模板方法,这个方法通常包含对具体方法的调用,抽象类还定义了一些抽象方法,这些方法会在模板方法中被调用,但具体的实现由子类来提供(抽象类,也就是父类中,将这些方法都设置为纯虚函数,子类要重写纯虚函数),子类通过继承抽象类并提供抽象方法的实现,从而可以自定义模板方法中的某些步骤,当模板方法被调用时,它会按照定义的顺序依次调用抽象类中的抽象方法和具体方法。

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define Convert(addrptr) ((struct sockaddr *)addrptr)

namespace Net_Work
{
    const static int defaultsockfd = -1;
    const int backlog = 5;
    enum
    {
        SocketError = 1,
        BindError,
        ListenError
    };
    // 封装一个基类, 套接字对应的接口类
    // 一旦一个类中有纯虚函数, 只有被继承并且实现此方法后, 才能创建对象

    // ::close 使用系统的接口函数

    // 设计模式:模板方法
    class Socket
    {
    public:
        virtual ~Socket() {}
        virtual void CreateSocketOrDie() = 0;            // 创建套接字
        virtual void BindSocketOrDie(uint16_t port) = 0; // 绑定
        virtual void ListenSocketOrDie(int backlog) = 0; // 监听
        // 获取连接, 拿到tcp所需的新的文件描述符 newsockfd, 并返回并 create 一个 tcp 套接字, 并将客户端信息通过输出型参数返回
        virtual Socket* AcceptConnection(std::string* peerip, uint16_t* peerport) = 0;
        virtual bool ConnetServer(std::string& serverip, uint16_t serverport) = 0;
        virtual int GetSockFd() = 0;
        virtual void SetSockFd(int sockfd) = 0;
        virtual void CloseSockfd() = 0;

    public:
        // 下面这些方法会被子类继承下去
        void BulidListenSocketMethod(uint16_t port, int backlog) // 创建监听套接字, 给 Server 用的
        {
            CreateSocketOrDie();
            BindSocketOrDie(port);
            ListenSocketOrDie(backlog);
        }
        bool BulidConnectSocketMethod(std::string& serverip, uint16_t serverport) // 创建连接套接字, 给Client 用
        {
            CreateSocketOrDie();
            return ConnetServer(serverip, serverport); // connet 有可能会失败
        }
        void BulidNormalSocketMethod(int sockfd)
        {
            SetSockFd(sockfd);
        }
    };

    // 成员函数用 override 修饰后, 派生类必须重载基类的同名虚函数, 否则编译不能通过

    class TcpSocket : public Socket
    {
    public:
        TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
        {
        }
        ~TcpSocket()
        {
        }
        void CreateSocketOrDie() override
        {
            _sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
                exit(SocketError);
        }
        void BindSocketOrDie(uint16_t port) override
        {
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = INADDR_ANY;
            local.sin_port = htons(port);

            int n = ::bind(_sockfd, Convert(&local), sizeof(local));
            if (n < 0)
                exit(BindError);
        }
        void ListenSocketOrDie(int backlog) override
        {
            int n = ::listen(_sockfd, backlog);
            if (n < 0)
                exit(ListenError);
        }
        Socket* AcceptConnection(std::string* peerip, uint16_t* peerport) override
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int newsockfd = ::accept(_sockfd, Convert(&peer), &len);
            if (newsockfd < 0)
                return nullptr;
            *peerport = ntohs(peer.sin_port);
            *peerip = inet_ntoa(peer.sin_addr);
            Socket* s = new TcpSocket(newsockfd);
            return s;
        }
        bool ConnetServer(std::string& serverip, uint16_t serverport) override
        {
            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(serverip.c_str());
            server.sin_port = htons(serverport);
            int n = ::connect(_sockfd, Convert(&server), sizeof(server));
            if (n == 0)
                return true;
            else
                return false;
        }
        int GetSockFd() override
        {
            return _sockfd;
        }
        void SetSockFd(int sockfd) override
        {
            _sockfd = sockfd;
        }
        void CloseSockfd() override
        {
            if (_sockfd > defaultsockfd)
                ::close(_sockfd);
        }

    private:
        int _sockfd;
    };
}

使用封装后的套接字实现Tcp服务器和客户端

实现Tcp服务器

#pragma once
#include "Socket.hpp"
#include <iostream>
#include <pthread.h>
#include <functional>

// void 表示返回值为空, ()里面是参数
using func_t = std::function<void(Net_Work::Socket* sockp)>;

class TcpServer;

class ThreadData
{
public:
    ThreadData(TcpServer* tcp_this, Net_Work::Socket* sockp) :_this(tcp_this), _sockp(sockp)
    {}
public:
    TcpServer* _this;
    Net_Work::Socket* _sockp;
};

class TcpServer
{
public:
    TcpServer(uint16_t port, func_t handler_request) :
        _port(port), _listensocket(new Net_Work::TcpSocket()), _handler_request(handler_request)
    {
        _listensocket->BulidListenSocketMethod(_port, Net_Work::backlog);
    }
    static void* ThreadRun(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        td->_this->_handler_request(td->_sockp); // 先回调,
        td->_sockp->CloseSockfd(); // 再关闭文件描述符
        delete td->_sockp;
        delete td;
    }
    void Loop()
    {
        while (true)
        {
            std::string peerip;
            uint16_t peerport;
            Net_Work::Socket* newsock = _listensocket->AcceptConnection(&peerip, &peerport);
            if (newsock == nullptr) continue;
            std::cout << "获取一个新连接 sockfd: " << newsock->GetSockFd() << " client info: " << peerip << ":" << peerport << std::endl;
            pthread_t tid;
            ThreadData* td = new ThreadData(this, newsock);
            pthread_create(&tid, nullptr, ThreadRun, td);
        }
    }
    ~TcpServer()
    {
        delete _listensocket;
    }
private:
    int _port;
    Net_Work::Socket* _listensocket;
    func_t _handler_request;
};

实现Tcp客户端

#include "Protocol.hpp"
#include "Socket.hpp"
#include <unistd.h>
#include <iostream>
#include <string>
#include <memory>

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cout << "Usage: " << argv[0] << " serverip serverport" << std::endl;
        return 0;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    Net_Work::Socket* s = new Net_Work::TcpSocket();

    if (!s->BulidConnectSocketMethod(serverip, serverport))
    {
        std::cerr << "connect " << serverip << ":" << serverport << "failed" << std::endl;
    }
    std::cout << "connect " << serverip << ":" << serverport << " success" << std::endl;
    std::unique_ptr<Factory> factory = std::make_unique<Factory>();
    std::shared_ptr<Request> req = factory->BuildRequest(10, 20, '+');
    while (true)
    {
        req->Inc();
        send(s->GetSockFd(), &(*req), sizeof(*req), 0);
        sleep(1);
    }

    s->CloseSockfd();
    return 0;
}

工厂模式

工厂设计模式是一种创建型设计模式,它提供了一种灵活的方式来实例化和组织对象的创建。工厂设计模式是一种创建对象的软件设计模式,它通过一个公共接口或基类来创建对象,而无需暴露对象的具体实现。这种设计模式的主要作用是将对象的创建与使用分离,从而降低耦合度,使代码更易于理解和维护。

  • 抽象产品(Abstract Product):定义了一个产品的接口或抽象类,它描述了产品的共同属性和方法。
  • 具体产品(Concrete Product):实现了抽象产品接口或继承自抽象产品类的具体类,代表了具体的对象。
  • 工厂类(Factory Class):负责创建具体产品的实例,它通常包含一个工厂方法,用于创建产品对象。
    优点:
  1. 解耦:将对象的创建与使用分离,降低了客户端与具体产品之间的耦合度。
  2. 灵活性和可扩展性:通过引入抽象层,可以轻松地创建新的具体产品类,而无需修改客户端代码。
  3. 代码复用:通过封装对象的创建过程,可以在多个客户端之间复用相同的创建逻辑。

简易的工厂模式:一个请求类,一个回应类,一个工厂类。

#include <iostream>
#include <memory>

// 请求
class Request
{
public:
    Request()
    {}
    Request(int x, int y, char op) :_data_x(x), _data_y(y), _oper(op)
    {}
private:
    // 约定好的 x _oper y
    int _data_x;
    int _data_y;
    char _oper; // + - * / %
};

// 相应
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> BuildRequest()
    {
        std::shared_ptr<Request> req = std::make_shared<Request>();
        return req;
    }
    std::shared_ptr<Request> BuildRequest(int x, int y, char op)
    {
        std::shared_ptr<Request> req = std::make_shared<Request>(x, y, op);
        return req;
    }

    std::shared_ptr<Response> BuildResponse()
    {
        std::shared_ptr<Response> resp = std::make_shared<Response>();
        return resp;
    }
    std::shared_ptr<Response> BuildResponse(int result, int code)
    {
        std::shared_ptr<Response> resp = std::make_shared<Response>(result, code);
        return resp;
    }
};

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

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

相关文章

百度ueditor如何修改图片的保存位置

背景 编辑器的保存图片是设置有默认规则的&#xff0c;但是服务器上一般会把图片路径设置为软连接&#xff0c;所以我就需要更改编辑器保存图片的路径&#xff0c;要不然&#xff0c;每次有新的部署&#xff0c;上一次上传的图片就会失效。先来看看编辑器默认的保存路径吧&…

目标检测算法之RT-DETR

RT-DETR算法理解 BackgroundModel ArchitectureEfficient Hybrid EncoderUncertainty-minimal Query Selection 总结 Background Real-time Detection Transformer&#xff08;RT-DETR&#xff09;是一个基于tranformer的实时推理目标检测模型。RT-DETR是2023年百度发布的一个…

七天速通javaSE:第五天 数组进阶

文章目录 前言一、二维数组二、Arrays类1.toString打印数组内各元素1.1 示例1.2 自己实现内部逻辑 2. sort升序排列3. fill数组填充&#xff08;重新赋值&#xff09;4.equals比较数组元素是否相等 三、冒泡排序 前言 本文将学习二维数组、arrays类以及冒泡排序 一、二维数组 …

重生奇迹MU新手攻略:如何一步步往大佬发展

装备强化攻略&#xff1a; 提纯装备&#xff1a;通过提纯装备可以提升基础属性&#xff0c;选择合适的装备进行提纯可以获得更好的效果。 镶嵌宝石&#xff1a;使用宝石进行装备镶嵌可以增加装备的属性&#xff0c;根据需要选择适合的宝石进行镶嵌。 洗练装备&#xff1a;通…

基于盲信号处理的声音分离——最大化信噪比的ICA算法

基于最大化信噪比的ICA算法是一种较新模式的ICA算法&#xff0c;在该算法中利用输出信号的信噪比建立信噪比函数作为该算法的代价函数。 在上式中&#xff0c;用S表示原信号&#xff0c;Y表示输出信号。由于原信号S并不知道&#xff0c;因此采用估计信号Y的滑动平均 来代替&…

激励视频广告的eCPM更高,每天的展示频次有限制吗?

在APP发展初期&#xff0c;由于DUA量级有限&#xff0c;所需的广告资源比较少&#xff0c;往往接入1-2家广告平台就能满足APP用户每日需要的广告展示量。而随着APP用户规模的扩大、广告场景的不断丰富&#xff0c;开发者要提升APP整体广告变现收益&#xff0c;一是可以尽可能多…

PLC数据采集案例

--------天津三石峰科技案例分享 项目介绍 项目背景 本项目为天津某钢铁集团下数字化改造项目&#xff0c;主要解决天津大型钢厂加氢站数字化改造过程中遇到的数据采集需求。项目难点PLC已经在运行了&#xff0c;需要采集里面数据&#xff0c;不修改程序&#xff0c;不影响P…

3D立体卡片动效(附源码)

3D立体卡片动效 欢迎关注&#xff1a;xssy5431 小拾岁月参考链接&#xff1a;https://mp.weixin.qq.com/s/9xEjPAA38pRiIampxjXNKQ 效果展示 思路分析 需求含有立体这种关键词&#xff0c;我们第一反应是采用动画中的平移、倾斜等实现。如果是立体&#xff0c;必然产生阴影&…

浅谈制造业EHS管理需要关注的重点

在快速发展的制造业中&#xff0c;EHS&#xff08;环境、健康、安全&#xff09;管理体系如同一道坚实的屏障&#xff0c;守护着企业的绿色与安全。那么&#xff0c;这个管理体系到底包含哪些内容呢&#xff1f;接下来&#xff0c;让我们一同探寻其奥秘。 一、EHS管理体系的丰富…

你的钱花得值不值?简谈FMEA培训的投资与回报

在探讨 FMEA&#xff08;失效模式及影响分析&#xff09;培训是否值得投资时&#xff0c;需要综合考虑多个方面。 从投资的角度来看&#xff0c;FMEA 培训通常需要一定的费用支出&#xff0c;包括培训课程的费用、培训期间员工的时间成本以及可能涉及的培训材料和设备成本。 然…

利用MMDetection将单阶段检测器作为Faster R-CNN的RPN

将单阶段检测器作为RPN 一、在 Faster R-CNN 中使用 FCOSHead 作为 RPNHead与原始配置的对比结果Neck (FPN)RPN HeadROI Head学习率 使用单阶段检测器作为RPN的优势1. 速度提升2. 准确性3. 简化架构4. 灵活性 二、评估候选区域三、用预先训练的 FCOS 训练定制的 Faster R-CNN 本…

Excel单元格输入逐字动态提示可选输入效果制作

Excel单元格输入逐字动态提示可选输入效果制作。INDEX函数整理动态列表&#xff0c;再配合IF函数干净界面&#xff0c;“数据验证”完成点选。 (笔记模板由python脚本于2024年06月27日 22:26:14创建&#xff0c;本篇笔记适合喜欢用Excel处理数据的coder翻阅) 【学习的细节是欢悦…

【数据集划分——针对于原先图片已经整理好类别】训练集|验证集|测试集

目标&#xff1a;用split-folders进行数据集划分 学习资源&#xff1a;https://www.youtube.com/watch?vC6wbr1jJvVs 努力的小巴掌 记录计算机视觉学习道路上的所思所得。 现在已经有了数据集&#xff0c;并且&#xff0c;注意&#xff0c;是已经划分好类别的&#xff01; …

基于ARM的通用的Qt移植思路

文章目录 实验环境介绍一、确认Qt版本二、确认交叉编译工具链三、配置Qt3.1、修改qmake.conf3.2、创建autoConfig.sh配置文件 四、编译安装Qt五、移植Qt安装目录六、配置Qt creator6.1、配置qmake6.2、配置GCC编译器6.3、配置G编译器6.4、配置编译器套件6.5、创建应用 七、总结…

MySQL 主从复制集群高可用

在实际的生产环境中&#xff0c;如果对数据库的读和写都在同一个数据库服务器中操作&#xff0c;无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。因此&#xff0c;一般来说 都是通过主从复制&#xff08;Master-Slave&#xff09;来同步数据&#x…

微信小程序毕业设计-线上教育商城系统项目开发实战(附源码+论文)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;微信小程序毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计…

基于STM32F103最小系统板和DL-LN33 2.4G通信 ZigBee无线串口自组网采集温湿度

文章目录 前言一、组网概述二、产品特性三、电气特性四、引脚配置五、UART通信协议5.1 UART参数5.2 包分割5.3 端口5.4 举例通信5.4.1 一个节点给另一个节点发送数据5.4.2 一个节点给另一个节点的内部端口发送数据5.4.3 一个节点给自己的内部端口发送数据5.4.4 不推荐的数据传输…

【单片机毕业设计选题24033】-基于STM32的智能饮水机设计

系统功能: 系统上电后显示“欢迎使用智能饮水系统请稍后”两秒后进入正常显示页面。 第一页面第一行显示“系统状态信息”&#xff0c;第二行显示温湿度信息&#xff0c;第三行显示 水温&水位值&#xff0c;第四行显示系统状态&#xff08;锁定或解锁状态&#xff09;。…

World of Warcraft [CLASSIC] Level 70 Dire Maul (DM)

[月牙钥匙] [大型爆盐炸弹] World of Warcraft [CLASSIC] Level 70 厄运之槌&#xff0c;完美贡品&#xff0c;Dire Maul &#xff08;DM&#xff09; Foror‘s Compendium of Dragon Slaying 佛洛尔的屠龙技术纲要 因为不是兽王宝宝&#xff0c;而且开始位置放的不对&am…

【python011】经纬度点位可视化html生成(有效方案)

1.熟悉、梳理、总结项目研发实战中的Python开发日常使用中的问题、知识点等&#xff0c;如获取省市等边界区域经纬度进行可视化&#xff0c;从而辅助判断、决策。 2.欢迎点赞、关注、批评、指正&#xff0c;互三走起来&#xff0c;小手动起来&#xff01; 3.欢迎点赞、关注、批…