服务器主函数
#include "EasyTcpServer.hpp"
#include<thread>
bool g_bRun = true;
void cmdThread()
{
while (true)
{
char cmdBuf[256] = {};
scanf("%s", cmdBuf);
if (0 == strcmp(cmdBuf, "exit"))
{
g_bRun = false;
printf("退出cmdThread线程\n");
break;
}
else {
printf("不支持的命令。\n");
}
}
}
class MyServer : public EasyTcpServer
{
public:
//只会被一个线程触发 安全
virtual void OnNetJoin(ClientSocket* pClient)
{
_clientCount++;
printf("client<%d> join\n", pClient->sockfd());
}
//cellServer 4 多个线程触发 不安全
//如果只开启1个cellServer就是安全的
virtual void OnNetLeave(ClientSocket* pClient)
{
_clientCount--;
printf("client<%d> leave\n", pClient->sockfd());
}
//cellServer 4 多个线程触发 不安全
//如果只开启1个cellServer就是安全的
virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header)
{
_recvCount++;
switch (header->cmd)
{
case CMD_LOGIN:
{
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);
}
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;
}
}
private:
};
int main()
{
MyServer server;
server.InitSocket();
server.Bind(nullptr, 4567);
server.Listen(5);
server.Start(4);
//启动UI线程
std::thread t1(cmdThread);
t1.detach();
while (g_bRun)
{
server.OnRun();
//printf("空闲时间处理其它业务..\n");
}
server.Close();
printf("已退出。\n");
getchar();
return 0;
}
MyServer 这个类,从EasyTcp这个类继承过来, 重写了几个虚函数。
Server的注册,绑定,监听函数很熟悉了,看看Start函数
void Start(int nCellServer)
{
for (int n = 0; n < nCellServer; n++)
{
auto ser = new CellServer(_sock);
_cellServers.push_back(ser);
//注册网络事件接受对象
ser->setEventObj(this);
//启动消息处理线程
ser->Start();
}
}
nCellServer是创建CellServer对象的个数。
创建n个CellServer, 添加到_cellServers数组中
注册这个事件
//网络事件接口
class INetEvent
{
public:
//纯虚函数
//客户端加入事件
virtual void OnNetJoin(ClientSocket* pClient) = 0;
//客户端离开事件
virtual void OnNetLeave(ClientSocket* pClient) = 0;
//客户端消息事件
virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header) = 0;
private:
};
启动服务
void Start()
{
_thread = std::thread(std::mem_fn(&CellServer::OnRun), this);
}
在看CellServer的OnRun方法
这里的CellServer 同样使用select监听客户端的读事件。如果有读事件,读取数据,同时检测断开连接的客户端。
//处理网络消息
bool OnRun()
{
while (isRun())
{
if (_clientsBuff.size() > 0)
{//从缓冲队列里取出客户数据
std::lock_guard<std::mutex> lock(_mutex);
for (auto pClient : _clientsBuff)
{
_clients.push_back(pClient);
}
_clientsBuff.clear();
}
//如果没有需要处理的客户端,就跳过
if (_clients.empty())
{
std::chrono::milliseconds t(1);
std::this_thread::sleep_for(t);
continue;
}
//伯克利套接字 BSD socket
fd_set fdRead;//描述符(socket) 集合
//清理集合
FD_ZERO(&fdRead);
//将描述符(socket)加入集合
SOCKET maxSock = _clients[0]->sockfd();
for (int n = (int)_clients.size() - 1; n >= 0; n--)
{
FD_SET(_clients[n]->sockfd(), &fdRead);
if (maxSock < _clients[n]->sockfd())
{
maxSock = _clients[n]->sockfd();
}
}
///nfds 是一个整数值 是指fd_set集合中所有描述符(socket)的范围,而不是数量
///既是所有文件描述符最大值+1 在Windows中这个参数可以写0
int ret = select(maxSock + 1, &fdRead, nullptr, nullptr, nullptr);
if (ret < 0)
{
printf("select任务结束。\n");
Close();
return false;
}
for (int n = (int)_clients.size() - 1; n >= 0; n--)
{
if (FD_ISSET(_clients[n]->sockfd(), &fdRead))
{
if (-1 == RecvData(_clients[n]))
{
auto iter = _clients.begin() + n;//std::vector<SOCKET>::iterator
if (iter != _clients.end())
{
if(_pNetEvent)
_pNetEvent->OnNetLeave(_clients[n]);
delete _clients[n];
_clients.erase(iter);
}
}
}
}
}
}
这里读取数据的方法,也考虑到处理粘包的方法
//缓冲区
char _szRecv[RECV_BUFF_SZIE] = {};
//接收数据 处理粘包 拆分包
int RecvData(ClientSocket* pClient)
{
// 5 接收客户端数据
int nLen = (int)recv(pClient->sockfd(), _szRecv, RECV_BUFF_SZIE, 0);
//printf("nLen=%d\n", nLen);
if (nLen <= 0)
{
//printf("客户端<Socket=%d>已退出,任务结束。\n", pClient->sockfd());
return -1;
}
//将收取到的数据拷贝到消息缓冲区
memcpy(pClient->msgBuf() + pClient->getLastPos(), _szRecv, nLen);
//消息缓冲区的数据尾部位置后移
pClient->setLastPos(pClient->getLastPos() + nLen);
//判断消息缓冲区的数据长度大于消息头DataHeader长度
while (pClient->getLastPos() >= sizeof(DataHeader))
{
//这时就可以知道当前消息的长度
DataHeader* header = (DataHeader*)pClient->msgBuf();
//判断消息缓冲区的数据长度大于消息长度
if (pClient->getLastPos() >= header->dataLength)
{
//消息缓冲区剩余未处理数据的长度
int nSize = pClient->getLastPos() - header->dataLength;
//处理网络消息
OnNetMsg(pClient, header);
//将消息缓冲区剩余未处理数据前移
memcpy(pClient->msgBuf(), pClient->msgBuf() + header->dataLength, nSize);
//消息缓冲区的数据尾部位置前移
pClient->setLastPos(nSize);
}
else {
//消息缓冲区剩余数据不够一条完整消息
break;
}
}
return 0;
}
先看OnRun()这个主方法
这个方法通过select IO多路复用来接收连接
//处理网络消息
bool OnRun()
{
if (isRun())
{
time4msg();
//伯克利套接字 BSD socket
fd_set fdRead;//描述符(socket) 集合
//清理集合
FD_ZERO(&fdRead);
//将描述符(socket)加入集合
FD_SET(_sock, &fdRead);
///nfds 是一个整数值 是指fd_set集合中所有描述符(socket)的范围,而不是数量
///既是所有文件描述符最大值+1 在Windows中这个参数可以写0
timeval t = { 0,10};
int ret = select(_sock + 1, &fdRead, 0, 0, &t); //
if (ret < 0)
{
printf("Accept Select任务结束。\n");
Close();
return false;
}
//判断描述符(socket)是否在集合中
if (FD_ISSET(_sock, &fdRead))
{
FD_CLR(_sock, &fdRead);
Accept();
return true;
}
return true;
}
return false;
}
然后看Accept函数
//接受客户端连接
SOCKET Accept()
{
// 4 accept 等待接受客户端连接
sockaddr_in clientAddr = {};
int nAddrLen = sizeof(sockaddr_in);
SOCKET cSock = INVALID_SOCKET;
#ifdef _WIN32
cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);
#else
cSock = accept(_sock, (sockaddr*)&clientAddr, (socklen_t *)&nAddrLen);
#endif
if (INVALID_SOCKET == cSock)
{
printf("socket=<%d>错误,接受到无效客户端SOCKET...\n", (int)_sock);
}
else
{
//将新客户端分配给客户数量最少的cellServer
addClientToCellServer(new ClientSocket(cSock));
//获取IP地址 inet_ntoa(clientAddr.sin_addr)
}
return cSock;
}
这个函数接收连接,将新来的客户端分配给客户数量最少的cellServer
下面看addClientToCellServer函数
void addClientToCellServer(ClientSocket* pClient)
{
//查找客户数量最少的CellServer消息处理对象
auto pMinServer = _cellServers[0];
for(auto pCellServer : _cellServers)
{
if (pMinServer->getClientCount() > pCellServer->getClientCount())
{
pMinServer = pCellServer;
}
}
pMinServer->addClient(pClient);
OnNetJoin(pClient);
}
对应的CellServer对象调用addClient方法,以及OnNetJoin函数
cellServer中的addClient函数,将客户端加入到客户端缓存队列中。
void addClient(ClientSocket* pClient)
{
std::lock_guard<std::mutex> lock(_mutex);
//_mutex.lock();
_clientsBuff.push_back(pClient);
//_mutex.unlock();
}
OnNetJoin函数将此时临界区的_clientCount的数量++
//只会被一个线程触发 安全
virtual void OnNetJoin(ClientSocket* pClient)
{
_clientCount++;
}
//cellServer 4 多个线程触发 不安全
//如果只开启1个cellServer就是安全的
virtual void OnNetLeave(ClientSocket* pClient)
{
_clientCount--;
}
//cellServer 4 多个线程触发 不安全
//如果只开启1个cellServer就是安全的
virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header)
{
_recvCount++;
}