目录
[16.连接池c api](#16.连接池c api)
1.知识点概述
2. 简单工厂模式 - 只需要一个工厂类
> 工厂: 使用一个单独的类来做创建实例的过程, 这就是工厂。
> 简单工厂:把对象的创建放到一个工厂类中,通过参数来创建不同的对象。
> 特点:
>
> - 缺点:每添一个对象,就需要对简单工厂进行修改(尽管不是删代码,仅仅是添一个switch case,但仍然违背了"不改代码"的原则)
> - 优点:去除了与具体产品的依赖, 实现简单。
>
```shell
简单工厂模式的使用:
-
创建一个工厂类
-
在这个类中提供一个公共的成员方法
-
创建对象, 一般情况下创建某些实现多态的子类对象
-
返回这个对象的地址
```
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
工厂模式流程
-
创建一个工厂类的基类
-
在这个基类中定义一个虚函数 -> 创建对象的方法
-
创建子工厂类(编解码的基类有多少子类, 就创建多少个子工厂类)
- 每个编解码的子类, 都对应一个工厂类
- 在子工厂类中重写工厂类基类中的虚函数
```
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个线程
-
工作的线程 -> N个
- 管理者
-
不工作(不处理业务逻辑, 监测工作的线程的状态, 管理线程的个数)
-
假设工作线程不够用了, 动态创建新的工作线程
-
建设工作的线程太多了, 销毁一部分工作的线程
-
动态监测工作的线程的状态
- 工作的线程
- 处理业务逻辑
- 需要一个任务队列
-
存储任务 -> 唤醒阻塞线程 -> 条件变量 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;
}
```