为server创建新的线程负责发送数据
目的:
- 并发性:如果服务器需要同时处理多个客户端的请求,使用单独的线程可以提高并发性。这样,一个线程可以专注于接收数据,而另一个线程可以专注于发送数据,从而避免了在单个线程中处理I/O操作时可能出现的阻塞。
- 性能:对于需要高吞吐量的服务器,使用单独的线程可以减少等待时间和上下文切换,从而提高性能。当一个线程等待网络操作完成时,另一个线程可以继续处理其他任务。
- 简化设计:在某些情况下,使用单独的线程可以简化服务器的设计。例如,如果发送和接收操作之间有复杂的依赖关系,将它们分开处理可能会更容易管理。
- 资源利用:在多核处理器上,使用多线程可以更好地利用系统资源,因为不同的线程可以在不同的核心上运行。
和之前的生产消费者模型一样,服务器收到数据后,把任务分给发送线程处理
1.创建了CELLTask.hpp文件
/*
v1.0
*/
#ifndef _CELL_TASK_H_
#include<thread>
#include<mutex>
#include<list>
//任务类型-基类,把任务本身抽象为一个基类,不同的任务可以继承该类,并重写自己的dotask方法
class CellTask
{
public:
CellTask()
{
}
//虚析构
virtual ~CellTask()
{
}
//执行任务
virtual void doTask()
{
}
private:
};
//执行任务的服务类型
class CellTaskServer
{
private:
//任务数据,拿到任务后,就遍历每个任务,让任务运行起来dotask
std::list<CellTask*> _tasks;
//任务数据缓冲区
std::list<CellTask*> _tasksBuf;
//改变数据缓冲区时需要加锁
std::mutex _mutex;
public:
//添加任务
void addTask(CellTask* task)
{
std::lock_guard<std::mutex> lock(_mutex);
_tasksBuf.push_back(task);
}
//启动工作线程
void Start()
{
//线程
std::thread t(std::mem_fn(&CellTaskServer::OnRun),this);
t.detach();
}
protected:
//工作函数
void OnRun()
{
while (true)
{
//从缓冲区取出数据
if (!_tasksBuf.empty())
{
std::lock_guard<std::mutex> lock(_mutex);
for (auto pTask : _tasksBuf)
{
_tasks.push_back(pTask);
}
_tasksBuf.clear();
}
//如果没有任务
if (_tasks.empty())
{
std::chrono::milliseconds t(1);
std::this_thread::sleep_for(t);
continue;
}
//处理任务
for (auto pTask : _tasks)
{
pTask->doTask();
delete pTask;
}
//清空任务
_tasks.clear();
}
}
};
#endif // !_CELL_TASK_H_
2.在server端添加任务类
//网络消息发送任务
class CellSendMsg2ClientTask:public CellTask //添加发送消息的任务类型
{
ClientSocket* _pClient;
DataHeader* _pHeader;
public:
CellSendMsg2ClientTask(ClientSocket* pClient, DataHeader* header)
{
_pClient = pClient;
_pHeader = header;
}
//执行任务
void doTask()
{
_pClient->SendData(_pHeader); //全部都在堆空间上创建,消息用完就释放
delete _pHeader;
}
};
3.在cellserver中添加CellTaskServer成员变量和添加发送任务方法
private:
//...
CellTaskServer _taskServer;
public:
void addSendTask(ClientSocket* pClient, DataHeader* header)
{
CellSendMsg2ClientTask* task = new CellSendMsg2ClientTask(pClient, header);
_taskServer.addTask(task);
}
4.在主服务器中事件类的OnNetMsg处理消息的方法中
class MyServer : public EasyTcpServer
{
public:
virtual void OnNetMsg(CellServer* pCellServer, ClientSocket* pClient, DataHeader* header)
{
EasyTcpServer::OnNetMsg(pCellServer, pClient, header);
switch (header->cmd)
{
case CMD_LOGIN:
{
//send recv
Login* login = (Login*)header;
//printf("收到客户端<Socket=%d>请求:CMD_LOGIN,数据长度:%d,userName=%s PassWord=%s\n", cSock, login->dataLength, login->userName, login->PassWord);
//忽略判断用户密码是否正确的过程
//LoginResult ret;
//pClient->SendData(&ret);
LoginResult* ret = new LoginResult();
pCellServer->addSendTask(pClient, ret);
}//接收 消息---处理 发送 生产者 数据缓冲区 消费者
break;
case CMD_LOGOUT:
{
Logout* logout = (Logout*)header;
//printf("收到客户端<Socket=%d>请求:CMD_LOGOUT,数据长度:%d,userName=%s \n", cSock, logout->dataLength, logout->userName);
//忽略判断用户密码是否正确的过程
//LogoutResult ret;
//SendData(cSock, &ret);
}
break;
default:
{
printf("<socket=%d>收到未定义消息,数据长度:%d\n", pClient->sockfd(), header->dataLength);
//DataHeader ret;
//SendData(cSock, &ret);
}
break;
}
}
};
这里,
- easytcpserver作为生产者,cellserver作为消费者
- cellserver作为生产者,CellTaskServer作为消费者
但是1没有使用统一的格式
现在的server收发流程