C++学习之工厂模式-套接字通信

目录

1.知识点概述

2.解决windows下动态库找不到的问题

3.编解码类图分析

4.通过protobuf生成需要的x++类

5.编解码基类codec实现

6.编解码类代码分析

7.工厂模式介绍

8.简单工厂模式实现-伪代码

9.工厂模式使用-伪代码

10.编解码类图

11.服务器通信效率分析

12.线程池设计思路

13.线程池相关

14.客户端效率优化

15.套接字连接池实现-伪代码

[16.连接池c api](#16.连接池c api)

17.套接字通信C语言api分析

18.套接字通信客户端类封装

19.服务器端C++类设计-有问题

20.服务器端类改进

21.封装之后的套接字类服务器和客户端通信流程

22.套接字类图


1.知识点概述

2. 简单工厂模式 - 只需要一个工厂类

> 工厂: 使用一个单独的类来做创建实例的过程, 这就是工厂。

> 简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。

> 特点:

>

> - 缺点:每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个switch case,但仍然违背了"不改代码"的原则)

> - 优点:去除了与具体产品的依赖, 实现简单。

>

```shell

简单工厂模式的使用:

  1. 创建一个工厂类

  2. 在这个类中提供一个公共的成员方法

  • 创建对象, 一般情况下创建某些实现多态的子类对象

  • 返回这个对象的地址

```

2.解决windows下动态库找不到的问题

```c++

// 通过创建工厂类, 添加工厂函数, 创建对象

// 两个编解码的子类

class RequestCodec : public Codec // 编解码请求数据

class RespondCodec : public Codec // 编解码响应数据

/*

知识点:

做条件判断的时候, if..else if .. else 效率比 switch 低

如果判断的情况比较少使用 if .. else

如果情况比较多, 建议使用 switch

*/

3.编解码类图分析

// 创建工厂类, 创建编解码对象

class Factory

{

public:

Factory();

~Factory();

// 工厂函数, 创建对象

// flag==1 -> RequestCodec

// falg==2 -> RespondCodec

Codec* createObject(int flag)

{

Codec* c = NULL;

// 判断

if(flag == 1)

{

c = new RequestCodec();

}

else if(flag == 2)

{

c = new RespondCodec();

}

return c;

}

}

```

4.通过protobuf生成需要的x++类

工厂类的使用:

```c++

// 1. 创建工厂类对象

Factory* fac = new Factory;

// 2. 通过工厂函数创建编解码对象

Codec* c = fac->createObject(1);

// 3. 编码

string str = c->encoceMsg();

```

3. 工厂模式 - 需要有N个工厂类

> 工厂方法:每种产品由一种工厂来创建,一个工厂保存一个new

>

> 特点:基本完美,完全遵循 "不改代码"的原则

```shell

工厂模式流程

  1. 创建一个工厂类的基类

  2. 在这个基类中定义一个虚函数 -> 创建对象的方法

  3. 创建子工厂类(编解码的基类有多少子类, 就创建多少个子工厂类)

  • 每个编解码的子类, 都对应一个工厂类
  1. 在子工厂类中重写工厂类基类中的虚函数

```

5.编解码基类codec实现

工厂类的使用:

```c++

// 两个编解码的子类

class RequestCodec : public Codec

class RespondCodec : public Codec

class TestCodec : public Codec // 编解码响应数据

```

使用

```c++

// 创建工厂类的基类

class BaseFactory

{

public:

BaseFactory();

~BaseFactory;

virtual Codec* createObject()

{

return NULL;

}

}

6.编解码类代码分析

// 工厂类子类

class RequestFactory : public BaseFactory

{

public:

RequestFactory();

~RequestFactory;

Codec* createObject()

{

return new RequestCodec;

}

}

class RespondFactory : public BaseFactory

{

public:

RespondFactory();

~RespondFactory;

Codec* createObject()

{

return new RespondCodec;

}

}

7.工厂模式介绍

class TestFactory : public BaseFactory

{

public:

TestFactory();

~TestFactory;

Codec* createObject()

{

return new TestCodec;

}

}

```

工厂模式使用

```c

// 1. 创建工厂类对象

BaseFactory* fac = new RespondFactory;

// 2. 得到了编解码对象

Codec* c = fac->createObject();

// 3. 编码

string str = c->encodeMsg();

```

8.简单工厂模式实现-伪代码

2. 套接字通信

2.1 通信效率问题

  • 服务器端

  • 单线程/单进程

  • 无法使用, 不支持多客户端

  • 多线程/多进程

  • 写程序优先考虑多线程

  • 什么时候考虑多进程:

  • 启动了一个可执行程序A, 要在A中启动可执行程序B

  • 支持多客户端连接

  • IO多路转接

  • 单线程/进程

  • 支持多客户端连接

  • 效率并不是最高的

  • 所有的客户端请求都是顺序处理的 -> 排队

  • 多线程

```c

void acceptConn(void* arg)

{

int fd = accept();

// fd添加到epoll树上

epoll_ctl();

}

void connClient(void* arg)

{

read();

write();

// 如果连接断开

epoll_crl(epfd, epoll_ctl_del, fd, NULL);

}

9.工厂模式使用-伪代码

// 服务器

int main()

{

// 1. 监听fd

int lfd = socket();

// 2. 绑定

bind();

// 3. 监听

listen();

// 4. 初始化epoll

int epfd = epoll_create(x);

// 5. epoll添加检测节点 -> lfd

epoll_ctl(epfd, epoll_ctl_add, lfd, ev);

struct epoll_event evs[1024];

while(1)

{

int num = epoll_wait(epfd, evs, 1024, NULL);

for(int i=0; i<num; ++i)

{

int curfd = evs[i].data.fd;

if(curfd == lfd)

{

pthread_create(&tid, NULL, acceptConn, &epfd);

}

else

{

// 这么传递curfd是错误的 -> 不能直接传地址

// 根据分析需要传递: curfd和epfd

pthread_create(&tid, NULL, connClient, &curfd);

}

}

}

}

```

10.编解码类图

  • 线程池

  • 多个线程的一个集合, 可以回收用完的线程

  • 线程池中线程的个数? -> 看业务逻辑

  • 密集型业务逻辑: 需要大量cup时间进行数据处理

  • 线程个数 == 当前电脑的cup核心数

  • 进行IO操作

  • 线程个数 = 2倍cup核心数

  • 不需要频繁的创建销毁线程

  • 设计思路

```c

  1. 需要两个角色
  • 管理者 -> 1个线程

  • 工作的线程 -> N个

  1. 管理者
  • 不工作(不处理业务逻辑, 监测工作的线程的状态, 管理线程的个数)

  • 假设工作线程不够用了, 动态创建新的工作线程

  • 建设工作的线程太多了, 销毁一部分工作的线程

  • 动态监测工作的线程的状态

  1. 工作的线程
  • 处理业务逻辑
  1. 需要一个任务队列
  • 存储任务 -> 唤醒阻塞线程 -> 条件变量 pthread_cond_broadcast/siganl

  • 工作的线程处理任务队列中的任务

  • 没有任务 -> 阻塞 pthread_cond_wait

```

11.服务器通信效率分析

客户端

  • 一般情况客户端只有一个连接和服务器通信

  • 没什么可以优化的

  • 客户端同时有多个连接和服务器通信

  • 第一个链接: 数据交换

  • 第2-6个连接: 下载

  • 提交效率的点:

  • 建立连接的时候: `connect`

  • 处理思路: -> 套接字连接池

  • 在进行业务通信之前, 先把需要的连接创建出来, 存储到一个容器中

  • 当前要通信的时候, 从容器中取出一个连接 (fd) -> 和服务器通信

  • 通信完成之后 -> 将这个连接放回到容器中

  • 套接字连接池实现思路

```c++

class ConnectPool

{

public:

ConnectPool(int number)

{

for(int i=0; i<number; ++i)

{

int fd = socket(); // 创建通信的fd

conect(); // 连接服务器

m_list.push(fd);

}

}

// 取出一个连接

int getConnect()

{

if(m_list.size() > 0)

{

int fd = m_list.head();

m_list.pop();

return fd;

}

return -1;

}

// 放回一个连接

void putConnect(int fd, bool isvaild)

{

m_list.push(fd);

}

~ConnectPool();

private:

queue<int> m_list;

}

```

12.线程池设计思路

2.2 C++类封装

  • c语言API

```c

// 客户端

// C API

int sckClient_init();

/* 客户端 连接服务器 */

int sckClient_connect(char *ip, int port, int connecttime, int *connfd);

/* 客户端 关闭和服务端的连接 */

int sckClient_closeconn(int connfd);

/* 客户端 发送报文 */

int sckClient_send(int connfd, int sendtime, unsigned char *data, int datalen);

/* 客户端 接受报文 */

int sckClient_rev(int connfd, int revtime, unsigned char **out, int *outlen); //1

/* 释放内存 */

int sck_FreeMem(void **buf);

/* 客户端 释放 */

int sckClient_destroy();

// 服务器端

/* 服务器端初始化 */

int sckServer_init(int port, int *listenfd);

int sckServer_accept(int listenfd, int timeout, int *connfd);

/* 服务器端发送报文 */

int sckServer_send(int connfd, int timeout, unsigned char *data, int datalen);

/* 服务器端端接受报文 */

int sckServer_rev(int connfd, int timeout, unsigned char **out, int *outlen); //1

int sckServer_close(int connfd);

/* 服务器端环境释放 */

int sckServer_destroy();

13.线程池相关

C++ 套接字类

  • 客户端

```c++

// 创建TcpSocket对象 == 一个连接, 这个对象就可以和服务器通信了, 多个连接需要创建多个这样的对象

class TcpSocket

{

public:

TcpSocket()

{

m_connfd = socket(af_inet, sock_stream, 0);

}

TcpSocket(int fd)

{

m_connfd = fd; // 传递进行的fd是可以直接通信的文件描述符, 不需要连接操作

}

~TcpSocket();

/* 客户端 连接服务器 */

int conectToHost(string ip, unsigned short port, int connecttime)

{

connect(m_connfd, &serverAddress, &len);

}

/* 客户端 关闭和服务端的连接 */

int disConnect();

/* 客户端 发送报文 */

int sendMsg(string sendMsg, int sendtime = 10000)

{

send(m_connfd, data, datadlen, 0);

}

/* 客户端 接受报文 */

string recvMsg(int timeout)

{

recv(m_connfd, buf, size, 0);

return string(buf);

}

private:

int m_connfd;

}

14.客户端效率优化

  • 服务器端

```c++

// 这个类不能用, 因为只能和一个客户端建立连接

class TcpServer

{

public:

// 初始化监听的套接字: 创建, 绑定, 监听

TcpServer();

~TcpServer(); // 在这里边关闭监听的fd

int acceptConn(int timeout);

/* 服务器 发送报文 */

int sendMsg(string sendMsg, int sendtime = 10000);

/* 服务器 接受报文 */

string recvMsg(int timeout);

int disConnect(); // 和客户端断开连接

private:

int m_lfd; // 监听的文件描述符

int m_connfd; // 通信的文件描述符

}

```

15.套接字连接池实现-伪代码

```c++

// 第二版

// 这个类不能用, 因为只能和一个客户端建立连接

class TcpServer

{

public:

// 初始化监听的套接字: 创建, 绑定, 监听

TcpServer();

~TcpServer(); // 在这里边关闭监听的fd

int acceptConn(int timeout);

/* 服务器 发送报文 */

int sendMsg(string sendMsg, int sendtime = 10000);

/* 服务器 接受报文 */

string recvMsg(int timeout);

int disConnect(); // 和客户端断开连接

private:

int m_lfd; // 监听的文件描述符

vector<int> m_connfd; // 不好用, 因为在接收和发送数据的时候, 不知道用安一个fd

}

```

16.连接池c api

```c++

// 第三个版本

// 思想: 服务端不负责通信, 只负责监听, 如果通信使用客户端类

class TcpServer

{

public:

// 初始化监听的套接字: 创建, 绑定, 监听

TcpServer();

~TcpServer(); // 在这里边关闭监听的fd

TcpSocket* acceptConn(int timeout = 90000)

{

int fd = accept(m_lfd, &address, &len);

// 通信fd -> 类

TcpSocket* tcp = new TcpSocket(fd);

if(tcp != NULL)

{

return tcp;

}

return NULL;

}

17.套接字通信C语言api分析

private:

int m_lfd; // 监听的fd

}

```

```c++

// TcpServer 使用

void* callback(void* arg)

{

TcpSocket* tcp = (TcpSocket* )arg;

// 通信

tcp->sendMsg();

tcp->recvMsg();

tcp->disConnect();

delete tcp;

}

// 套接字通信的服务器端程序

int main()

{

TcpServer* server = new TcpServer;

while(1)

{

TcpSocket* tcp = server->acceptConn();

// 创建子线程 -> 通信

pthread_crate(&tid, NULL, callback, tcp);

}

delete server;

return 0;

}

18.套接字通信客户端类封装

```c++

// tcp客户端程序

int main()

{

// 创建通信的套接字对象

TcpSocket* tcp = new TcpSocket;

// 连接服务器

tcp->conectToHost(ip, port, timeout);

// 通信

tcp->sendMsg();

tcp->recvMsg();

tcp->disConnect();

delete tcp;

}

```

19.服务器端C++类设计-有问题

20.服务器端类改进

21.封装之后的套接字类服务器和客户端通信流程

22.套接字类图

相关推荐
Chandler2418 分钟前
Go:方法
开发语言·c++·golang
whoarethenext3 小时前
qt的基本使用
开发语言·c++·后端·qt
_zsw4 小时前
Spring三级缓存学习
学习·spring·缓存
Amor风信子6 小时前
【大模型微调】如何解决llamaFactory微调效果与vllm部署效果不一致如何解决
人工智能·学习·vllm
虾球xz6 小时前
游戏引擎学习第220天
c++·学习·游戏引擎
愚润求学7 小时前
【C++】Stack && Queue && 仿函数
c++·stl·deque·queue·stack·priority queue
努力奋斗的小杨7 小时前
学习MySQL的第八天
数据库·笔记·学习·mysql·navicat
New个大鸭7 小时前
ATEngin开发记录_4_使用Premake5 自动化构建跨平台项目文件
c++·自动化·游戏引擎
空雲.8 小时前
牛客周赛88
数据结构·c++·算法
echola_mendes8 小时前
Streamlit性能优化:缓存与状态管理实战
学习