前言

  • 现在我们把服务端和客户端的代码修改一下,修改为两者互相发送与接收数据

一、服务端最终代码

  • 主要就是修改CellServer::OnNetMessage()中的代码,使其在接收到数据之后返回数据给客户端
#ifndef _EasyTcpClient_hpp_
#define _EasyTcpClient_hpp_

#ifdef _WIN32
#define FD_SETSIZE 10240
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS //for inet_pton()
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
//在Unix下没有这些宏,为了兼容,自己定义
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif


#ifndef RECV_BUFF_SIZE
#define RECV_BUFF_SIZE 10240 //接收缓冲区的大小
#endif // !RECV_BUFF_SIZE

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <vector>
#include <mutex>
#include <atomic>
#include <functional>
#include <map>
#include "MessageHeader.hpp"
#include "CELLTimestamp.hpp"
//using namespace std;

//客户端数据类型,用来封装一个客户端
class ClientSocket
{
public:
ClientSocket(SOCKET sockfd= INVALID_SOCKET) :_sock(sockfd), _lastPos(0) {
memset(_recvMsgBuff, 0, sizeof(_recvMsgBuff));
}

SOCKET sockfd() { return _sock; }
char *msgBuff() { return _recvMsgBuff; }
int getLastPos() { return _lastPos; }
void setLastPos(int pos) { _lastPos = pos; }

int SendData(DataHeader* header) {
if (header) {
return send(_sock, (const char*)header, header->dataLength, 0);
}
return SOCKET_ERROR;
}
private:
SOCKET _sock; //客户端socket
char _recvMsgBuff[RECV_BUFF_SIZE * 5]; //消息接收缓冲区
int _lastPos; //缓冲区的数据尾部位置
};

//网络事件接口
class INetEvent {
public:
virtual void OnClientJoin(ClientSocket* pClient) = 0; //客户端加入事件
virtual void OnClientLeave(ClientSocket* pClient) = 0; //客户端离开事件
virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header) = 0; //接收到客户端消息事件
virtual void OnNetRecv(ClientSocket* pClient) = 0; //recv事件
};

class CellServer
{
public:
CellServer(SOCKET sock = INVALID_SOCKET) :_sock(sock), maxSock(_sock), _pthread(nullptr), _pNetEvent(nullptr), _clients_change(false){
memset(_recvBuff, 0, sizeof(_recvBuff));
memset(&_fdRead_bak, 0, sizeof(_fdRead_bak));
}
~CellServer() { CloseSocket(); }
public:
bool isRun() { return _sock != INVALID_SOCKET; }
void CloseSocket();
public:
size_t getClientCount() { return _clients.size() + _clientsBuff.size(); } //返回当前客户端的数量
void setEventObj(INetEvent* event) { _pNetEvent = event; } //设置事件对象,此处绑定的是EasyTcpServer
public:
bool Onrun();
void AddClient(ClientSocket* pClient) //讲客户端加入到客户端连接缓冲队列中
{
//自解锁
std::lock_guard<std::mutex> lock(_mutex);
//_mutex.lock(); 当然也可以使用互斥锁
_clientsBuff.push_back(pClient);
//_mutex.unlock();
}
int RecvData(ClientSocket* pClient); //接收数据
void OnNetMessage(ClientSocket* pClient, DataHeader* header);//处理网络消息
void Start() {
//启动当前服务线程
//创建一个线程,线程执行函数为Onrun(),其实可以不传递this,但是为了更安全,可以传递this给Onrun()
_pthread = new std::thread(std::mem_fn(&CellServer::Onrun), this);
}
private:
SOCKET _sock; //服务端的套接字
std::map<SOCKET, ClientSocket*> _clients; //真正存储客户端
std::vector<ClientSocket*> _clientsBuff; //存储客户端连接缓冲队列,之后会被加入到_clients中去
SOCKET maxSock; //当前最大的文件描述符值,select的参数1要使用
char _recvBuff[RECV_BUFF_SIZE]; //接收缓冲区
std::mutex _mutex; //互斥锁
std::thread* _pthread; //当前子服务端执行的线程
INetEvent* _pNetEvent;
fd_set _fdRead_bak; //用来保存当前的fd_set
bool _clients_change;//当前是否有新客户端加入进来
};

class EasyTcpServer:public INetEvent
{
public:
EasyTcpServer() :_sock(INVALID_SOCKET), _msgCount(0), _recvCount(0), _clientCount(0){}
virtual ~EasyTcpServer() { CloseSocket(); }
public:
void InitSocket(); //初始化socket
int Bind(const char* ip, unsigned short port); //绑定端口号
int Listen(int n); //监听端口号
SOCKET Accept(); //接收客户端连接
void addClientToCellServer(ClientSocket* pClient);//将新客户加入到CellServer的客户端连接缓冲队列中
void Start(int nCellServer); //创建从服务器,并运行所有的从服务器。(参数为从服务器的数量)
void CloseSocket(); //关闭socket
bool isRun() { return _sock != INVALID_SOCKET; } //判断当前服务端是否在运行
bool Onrun(); //处理网络消息

void time4msg(); //每1秒统计一次收到的数据包的数量
public:
//客户端加入事件(这个是线程安全的,因为其只会被主服务器(自己)调用)
virtual void OnClientJoin(ClientSocket* pClient)override { _clientCount++; }
//客户端离开事件,这个里面做的事情比较简单,只是将当前客户端的数量--(如果从服务器不止一个,那么此函数不是线程安全的,因为这个函数会被多个从服务器调用的)
virtual void OnClientLeave(ClientSocket* pClient)override { _clientCount--; }
//接收到客户端消息事件,将数据包的数量增加(如果从服务器不止一个,那么此函数不是线程安全的,因为这个函数会被多个从服务器调用的)
virtual void OnNetMsg(ClientSocket* pClient, DataHeader* header)override { _msgCount++; }
//recv事件
virtual void OnNetRecv(ClientSocket* pClient)override { _recvCount++; }
private:
SOCKET _sock; //服务端套接字
std::vector<CellServer*> _cellServers; //存放从服务端对象
CELLTimestamp _tTime; //计时器
std::atomic_int _clientCount; //客户端的数量(这里采用一个原子操作,没什么特殊原因,使用玩玩罢了,下同)
std::atomic_int _msgCount; //表示服务端接收到客户端数据包的数量
std::atomic_int _recvCount; //recv()函数执行的次数
};

void EasyTcpServer::InitSocket()
{
#ifdef _WIN32
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
#endif

//建立socket
_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == _sock) {
std::cout << "Server:创建socket成功" << std::endl;
}
else {
std::cout << "Server:创建socket成功" << std::endl;
}
}

int EasyTcpServer::Bind(const char* ip, unsigned short port)
{
if (!isRun())
InitSocket();

//初始化服务端地址
struct sockaddr_in _sin = {};
#ifdef _WIN32
if (ip)
_sin.sin_addr.S_un.S_addr = inet_addr(ip);
else
_sin.sin_addr.S_un.S_addr = INADDR_ANY;
#else
if (ip)
_sin.sin_addr.s_addr = inet_addr(ip);
else
_sin.sin_addr.s_addr = INADDR_ANY;
#endif
_sin.sin_family = AF_INET;
_sin.sin_port = htons(port);

//绑定服务端地址
int ret = ::bind(_sock, (struct sockaddr*)&_sin, sizeof(_sin));
if (SOCKET_ERROR == ret) {
if (ip)
std::cout << "Server:绑定地址(" << ip << "," << port << ")失败!" << std::endl;
else
std::cout << "Server:绑定地址(INADDR_ANY," << port << ")失败!" << std::endl;
}
else {
if (ip)
std::cout << "Server:绑定地址(" << ip << "," << port << ")成功!" << std::endl;
else
std::cout << "Server:绑定地址(INADDR_ANY," << port << ")成功!" << std::endl;
}
return ret;
}

void EasyTcpServer::CloseSocket()
{
if (_sock != INVALID_SOCKET)
{
#ifdef _WIN32
closesocket(_sock);
WSACleanup();
#else
close(_sock);
#endif
_sock = INVALID_SOCKET;
}
}

int EasyTcpServer::Listen(int n)
{
//监听网络端口
int ret = listen(_sock, n);
if (SOCKET_ERROR == ret)
std::cout << "Server:监听网络端口失败!" << std::endl;
else
std::cout << "Server:监听网络端口成功!" << std::endl;
return ret;
}

SOCKET EasyTcpServer::Accept()
{
//用来保存客户端地址
struct sockaddr_in _clientAddr = {};
int nAddrLen = sizeof(_clientAddr);
SOCKET _cSock = INVALID_SOCKET;

//接收客户端连接
#ifdef _WIN32
_cSock = accept(_sock, (struct sockaddr*)&_clientAddr, &nAddrLen);
#else
_cSock = accept(_sock, (struct sockaddr*)&_clientAddr, (socklen_t*)&nAddrLen);
#endif
if (INVALID_SOCKET == _cSock) {
std::cout << "Server:接收到无效客户端!" << std::endl;
}
else {
//通知其他已存在的所有客户端,有新的客户端加入
//NewUserJoin newUserInfo(static_cast<int>(_cSock));
//SendDataToAll(&newUserInfo);

//将新连接的客户端加入到从服务器的客户端缓冲队列中
ClientSocket* newClient = new ClientSocket(_cSock);
addClientToCellServer(newClient);
OnClientJoin(newClient); //相应客户端加入事件,其函数会将客户端的数量++

//std::cout << "Server:接受到新的客户端(" << _clients.size() << ")连接,IP=" << inet_ntoa(_clientAddr.sin_addr)
// << ",Socket=" << static_cast<int>(_cSock) << std::endl;
}
return _cSock;
}

void EasyTcpServer::addClientToCellServer(ClientSocket* pClient)
{
//在_cellServers中寻找,哪一个CellServer其处理的客户数量最少,那么就将新客户加入到这个CellServer对象中去
auto pMinServer = _cellServers[0];
for (auto pCellServer : _cellServers)
{
if (pMinServer->getClientCount() > pCellServer->getClientCount())
{
pMinServer = pCellServer;
}
}
pMinServer->AddClient(pClient);
}

void EasyTcpServer::Start(int nCellServer)
{
for (int i = 0; i < nCellServer; ++i)
{
auto ser = new CellServer(_sock);
_cellServers.push_back(ser);
ser->setEventObj(this);
ser->Start();
}
}

bool EasyTcpServer::Onrun()
{
if (isRun())
{
time4msg(); //统计当前接收到的数据包的数量

fd_set fdRead;
//fd_set fdWrite;
//fd_set fdExp;
FD_ZERO(&fdRead);
//FD_ZERO(&fdWrite);
//FD_ZERO(&fdExp);
FD_SET(_sock, &fdRead);
//FD_SET(_sock, &fdWrite);
//FD_SET(_sock, &fdExp);

struct timeval t = { 0,0 };
int ret = select(_sock + 1, &fdRead, nullptr, nullptr, &t);
if (ret < 0)
{
std::cout << "Server:select出错!" << std::endl;
//select出错,那么就不能再继续运行select,出错之后,调用CloseSocket(),
//关闭所有服务端、及客所有户端套接字,那么isRun()就会返回false,从而终止server.cpp程序运行
CloseSocket();
return false;
}
if (FD_ISSET(_sock, &fdRead))//如果一个客户端连接进来,那么服务端的socket就会变为可读的,此时我们使用accept来接收这个客户端
{
FD_CLR(_sock, &fdRead);
Accept();
return true;
}
return true;
}
return false;
}

void EasyTcpServer::time4msg()
{
auto t = _tTime.getElapsedSecond();
if (t >= 1.0)
{
//msgCount,_recvCount为什么要除以t:
//因为我们要每1秒钟打印一次接收到的数据包,如果这个函数调用的时候时间差大于1秒,那么可以将recvCount/t,获得比较平均的数据包数量/recv执行次数
printf("time<%lf>,thread numer<%d>,client number<%d>,msgCount<%d>,recvCount<%d>\n",
t, _cellServers.size(), static_cast<int>(_clientCount), static_cast<int>(_msgCount / t), static_cast<int>(_recvCount / t));
_recvCount = 0;
_msgCount = 0;
_tTime.update();
}
}

void CellServer::CloseSocket()
{
if (_sock != INVALID_SOCKET)
{
#ifdef _WIN32
//将所有的客户端套接字关闭
for(auto iter:_clients)
{
closesocket(iter.second->sockfd());
delete iter.second;
}
//关闭服务端套接字
closesocket(_sock);

//因为这是从服务器,所以就不要清理套接字环境了,放置主服务器的套接字环境被清除
//WSACleanup();
#else
for (auto iter : _clients)
{
close(iter.second->sockfd());
delete iter.second;
}
close(_sock);
#endif
_clients.clear();
_sock = INVALID_SOCKET;
delete _pthread;
}
}

bool CellServer::Onrun()
{
while (isRun())
{
//如果客户端缓冲队列_clientsBuff中有新客户,那么就将其加入到_clients中
if (_clientsBuff.size() > 0)
{
//自解锁lock_guard,作用域结束后自动释放锁,因此if执行结束之后,_mutex就被释放了
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;
}

fd_set fdRead;
FD_ZERO(&fdRead);
//在主线程的select中已经对主服务端的_sock进行查询了,所以从服务器就不需要再将_sock加入到fd_set中了,否则两个地方同时操作会出错
//FD_SET(_sock, &fdRead);

//根据_fdRead_change判断是否有新客户端加入,如果有那么就进行新的FD_SET
if (_clients_change)
{
_clients_change = false;
for (auto iter : _clients)
{
FD_SET(iter.second->sockfd(), &fdRead);
if (maxSock < iter.second->sockfd())
maxSock = iter.second->sockfd();
}
//将更新后的fd_set保存到_fdRead_bak中
memcpy(&_fdRead_bak, &fdRead, sizeof(_fdRead_bak));
}
//否则直接拷贝,不用再循环FD_SET了
else
memcpy(&fdRead, &_fdRead_bak, sizeof(_fdRead_bak));


//从服务器一般只用来收取数据,所以这里设置为阻塞的也可以
//struct timeval t = { 1,0 };
int ret = select(maxSock + 1, &fdRead, nullptr, nullptr, nullptr);
if (ret < 0)
{
std::cout << "Server:select出错!" << std::endl;
CloseSocket();
return false;
}

#ifdef _WIN32
//如果是WIN下运行,fd_set拥有fd_count与fd_array成员
//我们可以遍历fd_set,然后从中获取数据,不需要使用FD_ISSET了
for (int n = 0; n < fdRead.fd_count; n++)
{
auto iter = _clients.find(fdRead.fd_array[n]);
//如果RecvData出错,那么就将该客户端从_client中移除
if (-1 == RecvData(iter->second))
{
if (_pNetEvent)
_pNetEvent->OnClientLeave(iter->second); //通知主服务器有客户端退出
delete iter->second;
_clients.erase(iter->first);
_clients_change = true; //这个要设置为true,因为有客户端退出了,需要重新进行FD_SET
}
}
#else
//如果在UNIX下,fd_set无fd_count与fd_array成员,我们只能遍历_clients数组
//遍历_clients map容器中所有的客户端,然后从中获取数据
for (auto iter : _clients)
{
//因为_clients是一个map,因此每次iter返回一个pair,其first成员为key(SOCKET),value成员为value(ClientSocket)
if (FD_ISSET(iter.second->sockfd(), &fdRead))
{
//如果RecvData出错,那么就将该客户端从_client中移除
if (-1 == RecvData(iter.second))
{
if (_pNetEvent)
_pNetEvent->OnClientLeave(iter.second); //通知主服务器有客户端退出
delete iter.second;
_clients.erase(iter.first);
_clients_change = true; //原因同上
}
}
}
#endif // _WIN32

}
return false;
}

int CellServer::RecvData(ClientSocket* pClient)
{
int _nLen = recv(pClient->sockfd(), _recvBuff, RECV_BUFF_SIZE, 0);
_pNetEvent->OnNetRecv(pClient);
if (_nLen < 0) {
//std::cout << "recv函数出错!" << std::endl;
return -1;
}
else if (_nLen == 0) {
//std::cout << "客户端<Socket=" << pClient->sockfd() << ">:已退出!" << std::endl;
return -1;
}

memcpy(pClient->msgBuff() + pClient->getLastPos(), _recvBuff, _nLen);
pClient->setLastPos(pClient->getLastPos() + _nLen);
while (pClient->getLastPos() >= sizeof(DataHeader))
{
DataHeader* header = (DataHeader*)pClient->msgBuff();
if (pClient->getLastPos() >= header->dataLength)
{
//剩余未处理消息缓冲区的长度
int nSize = pClient->getLastPos() - header->dataLength;
//处理网络消息
OnNetMessage(pClient, header);
//处理完成之后,将_recvMsgBuff中剩余未处理部分的数据前移
memcpy(pClient->msgBuff(), pClient->msgBuff() + header->dataLength, nSize);
pClient->setLastPos(nSize);
}
else {
//消息缓冲区剩余数据不够一条完整消息
break;
}
}
return 0;
}

void CellServer::OnNetMessage(ClientSocket* pClient, DataHeader* header)
{
//调用主服务OnNetMsg事件
_pNetEvent->OnNetMsg(pClient, header);

switch (header->cmd)
{
case CMD_LOGIN: //如果是登录
{
//Login *login = (Login*)header;
//std::cout << "服务端:收到客户端<Socket=" << pClient->sockfd() << ">的消息CMD_LOGIN,用户名:" << login->userName << ",密码:" << login->PassWord << std::endl;

//此处可以判断用户账户和密码是否正确等等(省略)

//返回登录的结果给客户端
LoginResult ret;
pClient->SendData(&ret);
}
break;
case CMD_LOGOUT: //如果是退出
{
//Logout *logout = (Logout*)header;
//std::cout << "服务端:收到客户端<Socket=" << pClient->sockfd() << ">的消息CMD_LOGOUT,用户名:" << logout->userName << std::endl;

//返回退出的结果给客户端
LogoutResult ret;
pClient->SendData(&ret);
}
break;
default: //如果有错误
{
//std::cout << "服务端:收到客户端<Socket=" << pClient->sockfd() << ">的未知消息消息" << std::endl;

//返回错误给客户端,DataHeader默认为错误消息
DataHeader ret;
pClient->SendData(&ret);
}
break;
}
}

#endif

二、客户端最终代码

  • 客户端代码如下,相比较于前一文的代码,主要的修改为将EasyTcpClient::SendData()内的send()函数改为每次发送nLen个字节,而不是1字节
#ifndef _EasyTcpClient_hpp_
#define _EasyTcpClient_hpp_

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS //for inet_pton()
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
//在Unix下没有这些宏,为了兼容,自己定义
#define SOCKET int
#define INVALID_SOCKET (SOCKET)(~0)
#define SOCKET_ERROR (-1)
#endif

//接收缓冲区的大小
#ifndef RECV_BUFF_SIZE
#define RECV_BUFF_SIZE 10240
#endif // !RECV_BUFF_SIZE


#include <iostream>
#include <string.h>
#include <stdio.h>
#include "MessageHeader.hpp"

using namespace std;

class EasyTcpClient
{
public:
EasyTcpClient() :_sock(INVALID_SOCKET), _isConnect(false), _lastPos(0) {
memset(_recvBuff, 0, sizeof(_recvBuff));
memset(_recvMsgBuff, 0, sizeof(_recvMsgBuff));
}
virtual ~EasyTcpClient() { CloseSocket(); }
public:
void InitSocket(); //初始化socket
void CloseSocket(); //关闭socket
bool Onrun(); //处理网络消息
bool isRun() { return ((_sock != INVALID_SOCKET) && _isConnect); } //判断当前客户端是否在运行
int ConnectServer(const char* ip, unsigned int port); //连接服务器
//使用RecvData接收任何类型的数据,然后将消息的头部字段传递给OnNetMessage()函数中,让其响应不同类型的消息
int RecvData(); //接收数据
virtual void OnNetMessage(DataHeader* header); //响应网络消息
int SendData(DataHeader* header, int nLen) ; //发送数据
private:
SOCKET _sock;
bool _isConnect; //当前是否连接
char _recvBuff[RECV_BUFF_SIZE]; //第一缓冲区(接收缓冲区),用来存储从网络缓冲区中接收的数据
char _recvMsgBuff[RECV_BUFF_SIZE * 5]; //第二缓冲区(消息缓冲区),将第一缓冲区中的数据存储在这个缓冲区中,并在这个缓冲区中对数据进行处理(粘包拆包处理)
int _lastPos; //用来标识当前消息缓冲区中数据的结尾位置
};

void EasyTcpClient::InitSocket()
{
//如果之前有连接了,关闭旧连接,开启新连接
if (isRun())
{
std::cout << "<Socket=" << (int)_sock << ">:关闭旧连接,建立了新连接" << std::endl;
CloseSocket();
}

#ifdef _WIN32
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
#endif

_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == _sock) {
std::cout << "ERROR:建立socket失败!" << std::endl;
}
else {
//std::cout << "<Socket=" << (int)_sock << ">:建立socket成功!" << std::endl;
}
}

int EasyTcpClient::ConnectServer(const char* ip, unsigned int port)
{
if (!isRun())
{
InitSocket();
}

//声明要连接的服务端地址(注意,不同平台的服务端IP地址也不同)
struct sockaddr_in _sin = {};
#ifdef _WIN32
_sin.sin_addr.S_un.S_addr = inet_addr(ip);
#else
_sin.sin_addr.s_addr = inet_addr(ip);
#endif
_sin.sin_family = AF_INET;
_sin.sin_port = htons(port);

//连接服务端
int ret = connect(_sock, (struct sockaddr*)&_sin, sizeof(_sin));
if (SOCKET_ERROR == ret) {
std::cout << "<Socket=" << (int)_sock << ">:连接服务端(" << ip << "," << port << ")失败!" << std::endl;
}
else {
_isConnect = true;
//std::cout << "<Socket=" << (int)_sock << ">:连接服务端(" << ip << "," << port << ")成功!" << std::endl;
}

return ret;
}

void EasyTcpClient::CloseSocket()
{
if (_sock != INVALID_SOCKET)
{
#ifdef _WIN32
closesocket(_sock);
WSACleanup();
#else
close(_sock);
#endif
_sock = INVALID_SOCKET;
_isConnect = false;
}
}

bool EasyTcpClient::Onrun()
{
if (isRun())
{
fd_set fdRead;
FD_ZERO(&fdRead);
FD_SET(_sock, &fdRead);

struct timeval t = { 0,0 };
int ret = select(_sock + 1, &fdRead, NULL, NULL, &t);
if (ret < 0)
{
std::cout << "<Socket=" << _sock << ">:select出错!" << std::endl;
return false;
}
if (FD_ISSET(_sock, &fdRead)) //如果服务端有数据发送过来,接收显示数据
{
FD_CLR(_sock, &fdRead);
if (-1 == RecvData())
{
std::cout << "<Socket=" << _sock << ">:数据接收失败,或服务端已断开!" << std::endl;
CloseSocket();
return false;
}
}
return true;
}
return false;
}

int EasyTcpClient::RecvData()
{
int _nLen = recv(_sock, _recvBuff, RECV_BUFF_SIZE, 0);
if (_nLen < 0) {
std::cout << "<Socket=" << _sock << ">:recv函数出错!" << std::endl;
return -1;
}
else if (_nLen == 0) {
std::cout << "<Socket=" << _sock << ">:接收数据失败,服务端已关闭!" << std::endl;
return -1;
}
//std::cout << "_nLen=" << _nLen << std::endl;

//将获取的数据拷贝到消息缓冲区
memcpy(_recvMsgBuff + _lastPos, _recvBuff, _nLen);
_lastPos += _nLen;

//如果_recvMsgBuff中的数据长度大于等于DataHeader
while (_lastPos >= sizeof(DataHeader))
{
DataHeader* header = (DataHeader*)_recvMsgBuff;
//如果_lastPos的位置大于等于一个数据包的长度,那么就会这个数据包进行处理
if (_lastPos >= header->dataLength)
{
//剩余未处理消息缓冲区的长度
int nSize = _lastPos - header->dataLength;
//处理网络消息
OnNetMessage(header);
//处理完成之后,将_recvMsgBuff中剩余未处理部分的数据前移
memcpy(_recvMsgBuff, _recvMsgBuff + header->dataLength, nSize);
_lastPos = nSize;
}
else {
//消息缓冲区剩余数据不够一条完整消息
break;
}
}
return 0;
}

void EasyTcpClient::OnNetMessage(DataHeader* header)
{
switch (header->cmd)
{
case CMD_LOGIN_RESULT: //如果返回的是登录的结果
{
LoginResult* loginResult = (LoginResult*)header;
//std::cout << "<Socket=" << _sock << ">,收到服务端数据:CMD_LOGIN_RESULT,数据长度:" << loginResult->dataLength << ",结果为:" << loginResult->result << std::endl;
}
break;
case CMD_LOGOUT_RESULT: //如果是退出的结果
{
LogoutResult* logoutResult = (LogoutResult*)header;
//std::cout << "<Socket=" << _sock << ">,收到服务端数据:CMD_LOGOUT_RESULT,数据长度:" << logoutResult->dataLength << ",结果为:" << logoutResult->result << std::endl;
}
break;
case CMD_NEW_USER_JOIN: //有新用户加入
{
NewUserJoin* newUserJoin = (NewUserJoin*)header;
//std::cout << "<Socket=" << _sock << ">,收到服务端数据:CMD_NEW_USER_JOIN,数据长度:" << newUserJoin->dataLength << ",新用户Socket为:" << newUserJoin->sock << std::endl;
}
break;
case CMD_ERROR: //错误消息
{
//错误消息的类型就是DataHeader的,因此直接使用header即可
//std::cout << "<Socket=" << _sock << ">,收到服务端数据:CMD_ERROR,数据长度:" << header->dataLength << std::endl;
}
break;
default:
{
//std::cout << "<Socket=" << _sock << ">,收到服务端数据:未知类型的消息,数据长度:" << header->dataLength << std::endl;
}
}
}

int EasyTcpClient::SendData(DataHeader* header,int nLen)
{
int ret = SOCKET_ERROR;
if (isRun() && header)
{
ret = send(_sock, (const char*)header, nLen, 0);
if (ret == SOCKET_ERROR) {
CloseSocket();
printf("Client:socket<%d>发送数据错误,关闭客户端连接\n", static_cast<int>(_sock));
}
}
return ret;
}

#endif // !_EasyTcpClient_hpp_

三、服务端最终测试程序

  • 代码与前文的文章相同,基本不变
#include "EasyTcpServer.hpp"
#include "MessageHeader.hpp"

int main()
{
EasyTcpServer server;
server.Bind("192.168.0.105", 4567);
server.Listen(5);
server.Start(4);

while (server.isRun())
{
server.Onrun();
}

server.CloseSocket();
std::cout << "服务端停止工作!" << std::endl;

getchar(); //防止程序一闪而过
return 0;
}

四、客户端最终测试程序

  • 代码与前文的文章相同,也基本不变,在发送数据之后也接收服务器来的
#include "EasyTcpClient.hpp"
#include "CELLTimestamp.hpp"
#include <thread>
#include <atomic>

bool g_bRun = false;
const int cCount = 10000; //客户端的数量
const int tCount = 4; //线程的数量
std::atomic_int sendCount = 0; //send()函数执行的次数
std::atomic_int readyCount = 0;//代表已经准备就绪的线程数量
EasyTcpClient* client[cCount]; //客户端的数组

void cmdThread();
void sendThread(int id);

int main()
{
g_bRun = true;

//UI线程,可以输入命令
std::thread t(cmdThread);
t.detach();

//启动发送线程
for (int n = 0; n < tCount; ++n)
{
std::thread t(sendThread, n + 1);
t.detach();
}

//每1秒中打印一次信息(其中包括send()函数的执行次数)
CELLTimestamp tTime;
while (true)
{
auto t = tTime.getElapsedSecond();
if (t >= 1.0)
{
printf("time<%lf>,thread numer<%d>,client number<%d>,sendCount<%d>\n",
t, tCount, cCount, static_cast<int>(sendCount / t));
sendCount = 0;
tTime.update();
}
Sleep(1);
}

return 0;
}

void cmdThread()
{
char cmdBuf[256] = {};
while (true)
{
std::cin >> cmdBuf;
if (0 == strcmp(cmdBuf, "exit"))
{
g_bRun = false;
break;
}
else {
std::cout << "命令不识别,请重新输入" << std::endl;
}
}
}

void sendThread(int id)
{
/*
下面这几个变量是为了平均每个线程创建的客户端的数量:
例如,本次测试时客户端数量为1000,线程数量为4,那么每个线程应该创建250个客户端
线程1:c=250,begin=0,end=250
线程2:c=250,begin=250,end=500
线程3:c=250,begin=500,end=750
线程4:c=250,begin=750,end=1000
*/
int c = cCount / tCount;
int begin = (id - 1)*c;
int end = id*c;

for (int n = begin; n < end; ++n) //创建客户端
{
client[n] = new EasyTcpClient;
}
for (int n = begin; n < end; ++n) //让每个客户端连接服务器
{
client[n]->ConnectServer("192.168.0.105", 4567);
}
printf("Thread<%d>,Connect=<begin=%d, end=%d>\n", id, (begin + 1), end);


//将readyCount,然后判断readyCount是否达到了tCount
//如果没有,说明所有的线程还没有准备好,那么就等待所有线程都准备好一起返回发送数据
readyCount++;
while (readyCount < tCount)
{
std::chrono::microseconds t(10);
std::this_thread::sleep_for(t);
}

//这里定义为数组,可以随根据需求修改客户端单次发送给服务端的数据包数量
const int nNum = 1;
Login login[nNum];
for (int n = 0; n < nNum; ++n)
{
strcpy(login[n].userName, "dongshao");
strcpy(login[n].PassWord, "123456");
}
//在外面定义nLen,就不用每次在for循环中SendData时还要去sizeof计算一下login的大小
int nLen = sizeof(login);
//循环向服务端发送消息
while (g_bRun)
{
for (int n = begin; n < end; ++n)
{
if (client[n]->SendData(login, nLen) != SOCKET_ERROR)
{
sendCount++;
}
client[n]->Onrun();
}
}

//关闭客户端
for (int n = begin; n < end; ++n)
{
client[n]->CloseSocket();
delete client[n];
}

printf("thread:all clients close the connection!\n");
}

五、测试结果如下

  • 左侧为服务端,右侧为客户端

项目(百万并发网络通信架构)10.4---服务端数据收发的性能瓶颈_服务端