服务器主程序函数,非常清晰
#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");
}
}
}
int main()
{
EasyTcpServer server;
server.InitSocket();
server.Bind(nullptr, 4567);
server.Listen(5);
server.Start();
//启动UI线程
std::thread t1(cmdThread);
t1.detach();
while (g_bRun)
{
server.OnRun();
//printf("空闲时间处理其它业务..\n");
}
server.Close();
printf("已退出。\n");
getchar();
return 0;
}
server这个类封装了服务器的业务逻辑,从这个类入手
class EasyTcpServer : public INetEvent
{
private:
SOCKET _sock; // 服务器socket
std::vector<ClientSocket*> _clients; // 客户端对象数组
std::vector<CellServer*> _cellServers; // 客户端服务数组
CELLTimestamp _tTime; // 定时器
}
这里EasyTcpServer继承了INetEvent类,这是一个抽象类,目的还不清楚。
class INetEvent
{
public:
//纯虚函数
//客户端离开事件
virtual void OnLeave(ClientSocket* pClient) = 0;
virtual void OnNetMsg(SOCKET cSock, DataHeader* header) = 0;
private:
};
首先来看Start函数
void Start()
{
for (int n = 0; n < _CellServer_THREAD_COUNT; n++)
{
auto ser = new CellServer(_sock);
_cellServers.push_back(ser);
ser->setEventObj(this);
ser->Start();
}
}
#define _CellServer_THREAD_COUNT 4
这个函数先开启了4个服务线程
再去看onRun函数
bool OnRun()
{
if (isRun())
{
time4msg();
//伯克利套接字 BSD socket
fd_set fdRead;//描述符(socket) 集合
//fd_set fdWrite;
//fd_set fdExp;
//清理集合
FD_ZERO(&fdRead);
//FD_ZERO(&fdWrite);
//FD_ZERO(&fdExp);
//将描述符(socket)加入集合
FD_SET(_sock, &fdRead);
//FD_SET(_sock, &fdWrite);
//FD_SET(_sock, &fdExp);
///nfds 是一个整数值 是指fd_set集合中所有描述符(socket)的范围,而不是数量
///既是所有文件描述符最大值+1 在Windows中这个参数可以写0
timeval t = { 0,10};
int ret = select(_sock + 1, &fdRead, 0, 0, &t); //
//printf("select ret=%d count=%d\n", ret, _nCount++);
if (ret < 0)
{
printf("select任务结束。\n");
Close();
return false;
}
//判断描述符(socket)是否在集合中
if (FD_ISSET(_sock, &fdRead))
{
FD_CLR(_sock, &fdRead);
Accept();
return true;
}
return true;
}
return false;
}
这里OnRun函数只接受连接,不再处理消息
这里接受到连接,直接扔到处理CellServer
//接受客户端连接
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
{
//NewUserJoin userJoin;
//SendDataToAll(&userJoin);
addClientToCellServer(new ClientSocket(cSock));
//printf("socket=<%d>新客户端<%d>加入:socket = %d,IP = %s \n", (int)_sock, _clients.size(),(int)cSock, inet_ntoa(clientAddr.sin_addr));
}
return cSock;
}
下面来看这个addClientToCellServer
有很多个处理线程,扔给一个最简单的处理线程
void addClientToCellServer(ClientSocket* pClient)
{
_clients.push_back(pClient);
//查找客户数量最少的CellServer消息处理对象
auto pMinServer = _cellServers[0];
for(auto pCellServer : _cellServers)
{
if (pMinServer->getClientCount() > pCellServer->getClientCount())
{
pMinServer = pCellServer;
}
}
pMinServer->addClient(pClient);
}
这里简单封装一个客户端的信息,包括客户端fd和一个客户端消息缓冲区。
//缓冲区最小单元大小
#ifndef RECV_BUFF_SZIE
#define RECV_BUFF_SZIE 10240
#endif // !RECV_BUFF_SZIE
#define _CellServer_THREAD_COUNT 4
class ClientSocket
{
public:
ClientSocket(SOCKET sockfd = INVALID_SOCKET)
{
_sockfd = sockfd;
memset(_szMsgBuf, 0, sizeof(_szMsgBuf));
_lastPos = 0;
}
SOCKET sockfd()
{
return _sockfd;
}
char* msgBuf()
{
return _szMsgBuf;
}
int getLastPos()
{
return _lastPos;
}
void setLastPos(int pos)
{
_lastPos = pos;
}
private:
// socket fd_set file desc set
SOCKET _sockfd;
//第二缓冲区 消息缓冲区
char _szMsgBuf[RECV_BUFF_SZIE * 10];
//消息缓冲区的数据尾部位置
int _lastPos;
};
客户端服务对象
class CellServer
{
private:
SOCKET _sock;
//正式客户队列
std::vector<ClientSocket*> _clients;
//缓冲客户队列
std::vector<ClientSocket*> _clientsBuff;
std::mutex _mutex;
std::thread* _pThread;
INetEvent* _pNetEvent;
public:
std::atomic_int _recvCount;
};
最后一个参数要处理的事件类型
其中有原子类型的共享数据_recvCount;
下面看一个重要的函数,将处理客户端添加到缓冲客户队列中
void addClient(ClientSocket* pClient)
{
std::lock_guard<std::mutex> lock(_mutex);
//_mutex.lock();
_clientsBuff.push_back(pClient);
//_mutex.unlock();
}
最关键的是消息处理的onRun函数, 这个函数再处理消息中,又使用select做IO多路复用。
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->OnLeave(_clients[n]);
delete _clients[n];
_clients.erase(iter);
}
}
}
}
}
}
其中接受消息依然又粘包的处理
void Start()
{
_pThread = new std::thread(std::mem_fun(&CellServer::OnRun), this);
}