前言
一、生产者与消费者设计模式
- 某个模块负责产生数据,这些数据由另一个模块来负责处理:
- 产生数据的模块,就形象地称为生产者
- 而处理数据的模块,就称为消费者
- 该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据
- 缓冲区的作用:
- 1.解耦,生产者和消费者只依赖缓冲区,而不相互依赖
- 2.支持并发和异步
二、服务端模型图
![在这里插入图片描述](https://img-blog.csdnimg.cn/ddedef64afe343d488f92e1298c2603e.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAcXEyMzAwMTE4Ng==,size_20,color_FFFFFF,t_70,g_se,x_16)
三、服务端代码优化
1、分离客户端连接与消息处理业务
- 原先的代码是将每个客户端保存在EasyTcpServer的数组中,并且在EasyTcpServer::Onrun()函数中调用select对每个客户端进行数据监听
- 现在的代码为:
- 保持EasyTcpServer不变,作为生产者,其内部创建一个CellServer对象数组
- 但是客户端不在EasyTcpServer内存储了,创建一个新的class CellServer,作为消费者,其保存客户端的连接,并在CellServer::Onrun()函数中调用select对每个客户端进行数据监听
- EasyTcpServer只接收客户端的连接,接收到客户端的连接之后,将其传递给自己CellServer对象数组中的某一个CellServer对象进行管理
2、为消息处理线程添加新客户端缓冲队列
- 新增一个class CellServer
- 作为消费者,从缓冲区中查看是否有新客户端,如果有那么就将新客户端添加到自己的fd_set中进行select监听
- 新增一个addClient方法,用来向缓冲队列中添加新的客户端
class CellServer
{
public:
CellServer(SOCKET sock = INVALID_SOCKET)
{
_sock = sock;
}
~CellServer()
{
Close();
_sock = INVALID_SOCKET;
}
SOCKET _maxSock;
bool OnRun()
{
_clients_change = true;
while (isRun())
{
if (_clientsBuff.size() > 0)
{
std::lock_guard<std::mutex> lock(_mutex);
for (auto pClient : _clientsBuff)
{
_clients[pClient->sockfd()] = pClient;
}
_clientsBuff.clear();
_clients_change = true;
}
}
}
void addClient(ClientSocket* pClient)
{
std::lock_guard<std::mutex> lock(_mutex);
_clientsBuff.push_back(pClient);
}
private:
SOCKET _sock;
std::map<SOCKET, ClientSocket*> _clients;
std::vector<ClientSocket*> _clientsBuff;
std::mutex _mutex;
};
3、建立消息处理线程
- EasyTcpServer在EasyTcpServer::Start()函数中创建指定数量的CellServer消费者对象,然后调用CellServer.Start()函数
- 每个CellServer对象的Start()函数创建一个线程,线程的执行函数为自己的OnRun()函数
- OnRun()函数中调用select对每个客户端进行数据监听
a)EasyTcpServer::Start()
void Start(int nCellServer)
{
for (int n = 0; n < nCellServer; n++)
{
auto ser = new CellServer(_sock);
_cellServers.push_back(ser);
ser->Start();
}
}
b)CellServer::Start()
void Start()
{
_thread = std::thread(std::mem_fn(&CellServer::OnRun), this);
}
4、将新客户端分配给客户端数量最少的消息线程
- EasyTcpServer::Accept()接收到一个新的客户端之后,调用EasyTcpServer::addClientToCellServer()将新的客户端加入到CellServer的_clientsBuff缓冲队列中
- 放入到CellServer的_clientsBuff缓冲队列中的时候,通过调用每个CellServer对象的getClientCount()函数,如果哪个CellServer对象所管理的客户端最少,那么就将这个新的客户端放入到哪个CellServer对象的_clientsBuff缓冲队列中
a)CellServer::getClientCount
size_t getClientCount()
{
return _clients.size() + _clientsBuff.size();
}
b)EasyTcpServer::Accept
SOCKET Accept()
{
if (INVALID_SOCKET == cSock)
{
printf("socket=<%d>错误,接受到无效客户端SOCKET...\n", (int)_sock);
}
else
{
addClientToCellServer(new ClientSocket(cSock));
}
return cSock;
}
c)EasyTcpServer::addClientToCellServer
void addClientToCellServer(ClientSocket* pClient)
{
_clients.push_back(pClient);
auto pMinServer = _cellServers[0];
for (auto pCellServer : _cellServers)
{
if (pMinServer->getClientCount() > pCellServer->getClientCount())
{
pMinServer = pCellServer;
}
}
pMinServer->addClient(pClient);
}
5、消息处理线程在无客户端时休眠1毫秒
- CellServer::OnRun()中如果_clients为空,那么说明无客户端,那么休眠一秒继续运行
- 注意这里不要直接使用Sleep,应该使用标准库提供的休眠方法,这样才可以跨平台
bool OnRun()
{
_clients_change = true;
while (isRun())
{
if (_clientsBuff.size() > 0)
{
std::lock_guard<std::mutex> lock(_mutex);
for (auto pClient : _clientsBuff)
{
_clients[pClient->sockfd()] = pClient;
}
_clientsBuff.clear();
_clients_change = true;
}
if (_clients.empty())
{
std::chrono::milliseconds t(1);
std::this_thread::sleep_for(t);
continue;
}
6、为消息处理线程添加每秒收包计数
- 在EasyTcpServer中定义一个_recvCount变量,用来表示服务端接收到客户端数据包的数量
- _recvCount在EasyTcpServer::OnNetMsg()函数中被++
- CellServer::OnNetMsg()函数每收到一次消息,那么调用其绑定的EasyTcpServer对象的OnNetMsg()函数,将_recvCount++
- EasyTcpServer::Onrun()函数每执行一次,调用一次EasyTcpServer::time4msg()函数,EasyTcpServer::time4msg()函数统计_recvCount的信息并打印
void time4msg()
{
auto t1 = _tTime.getElapsedSecond();
if (t1 >= 1.0)
{
printf("thread<%d>,time<%lf>,socket<%d>,clients<%d>,recvCount<%d>\n", _cellServers.size(), t1, _sock, (int)_clientCount, (int)(_recvCount / t1));
_recvCount = 0;
_tTime.update();
}
}
virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header)
{
_recvCount++;
}
7、事件通知,有客户端退出
a)client连接时增加一个延迟
void sendThread(int id)
{
for (int n = begin; n < end; n++)
{
client[n]->Connect("127.0.0.1", 4567);
printf("thread<%d>,Connect=%d\n", id, n);
}
std::chrono::milliseconds t(5000);
std::this_thread::sleep_for(t);