C++ High Concurrency Network Architecture and Implementation-Part 5

table of Contents

The idea of ​​realizing sticky package subcontracting

1. Solve the problem of client sticking and subcontracting

Second, solve the problem of server sticking and subcontracting

Third, break through the limitation of 64 connections under windows, and add high-precision timer function


Achieve the task today

  1. Solve the problem of client sticking and subcontracting
  2. Solve the problem of client sticking and subcontracting
  3. Break through the select64 limitation under Windows
  4. Add high-precision timer measurement processing capability

The idea of ​​realizing sticky package subcontracting

It is the idea shown in the figure. The received data is stored in a custom buffer first, and then the buffer is processed, so as to achieve the problem of sticking and sub-packaging


1. Solve the problem of client sticking and subcontracting

Client code:

  • EasyTcpClient.hpp
#ifndef _EasyTcpClient_hpp_
#define _EasyTcpClient_hpp_

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
#include<Winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET       (SOCKET)(~0)
#define SOCKET_ERROR                 (-1)
#endif

#include<iostream>
#include "MessageHeader.hpp"

using namespace std;

class EasyTcpClient
{
	SOCKET _sock;
public:
	EasyTcpClient()
	{
		_sock == INVALID_SOCKET;
	}

	virtual ~EasyTcpClient()
	{
		Close();
	}

	//初始化socket
	void InitSocket()
	{
#ifdef _WIN32
		//启动Windows socket 2.x环境
		WORD ver = MAKEWORD(2, 2);
		WSADATA dat;
		WSAStartup(ver, &dat);
#endif
		//1,用Socket API建立建立TCP客户端
		if (_sock == INVALID_SOCKET)
		{
			cout << "_sock=" << _sock << "关闭旧连接" << endl;
			Close();
		}
		_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		if (_sock == INVALID_SOCKET)
		{
			cout << "建立socket失败" << endl;
		}
		else
		{
			cout << "建立socket成功" << endl;
		}
	}

	//连接服务器
	int Connect(char *ip, unsigned short port)
	{
		if (_sock != INVALID_SOCKET)
		{
			InitSocket();
		}
		//2,连接服务器 connect
		sockaddr_in _sin = {};
		_sin.sin_family = AF_INET;
		_sin.sin_port = htons(4567);

#ifdef _WIN32
		_sin.sin_addr.S_un.S_addr = inet_addr(ip);
#else
		_sin.sin_addr.s_addr = inet_addr(ip);
#endif
		int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
		if (SOCKET_ERROR == ret)
		{
			cout << "错误,连接Socket失败" << endl;
		}
		else
		{
			cout << "连接Socket成功" << endl;
		}
		return 0;
	}

	//7,关闭套接字closesocket
	void Close()
	{
		if (_sock != INVALID_SOCKET)
		{
#ifdef _WIN32
			closesocket(_sock);
			//清除Windows socket环境
			WSACleanup();
#else
			close(_sock);
#endif
		}
	}

	//处理网络消息
	bool OnRun()
	{
		if (isRun())
		{
			fd_set fdReads;
			FD_ZERO(&fdReads);
			FD_SET(_sock, &fdReads);

			//添加非阻塞
			timeval t = { 1, 0 };

			int ret = select(_sock + 1, &fdReads, 0, 0, NULL);
			if (ret < 0)
			{
				cout << "_sock=" << _sock << " 任务结束1" << endl;
				return false;
			}
			if (FD_ISSET(_sock, &fdReads))
			{
				FD_CLR(_sock, &fdReads);

				if (-1 == RecvData(_sock))
				{
					cout << "_sock=" << _sock << " 任务结束2" << endl;
					return false;
				}
			}
			return true;
		}
		return false;
	}

	//是否工作中
	bool isRun()
	{
		return _sock != INVALID_SOCKET;
	}

//缓冲区最小单元大小
#ifndef RECV_BUFF_SIZE
#define RECV_BUFF_SIZE 10240
#endif

	//接收缓冲区
	char _szRecv[RECV_BUFF_SIZE];
	//第二缓冲区,消息缓冲区
	char _szMsgBuf[RECV_BUFF_SIZE*10];
	//消息缓冲区的数据尾部位置
	int _lastPos = 0;
	//接收数据 处理粘包,拆分包
	int RecvData(SOCKET cSock)
	{
		//接收消息
		int nLen = (int)recv(cSock, _szRecv, RECV_BUFF_SIZE, 0);
		//接收消息为零
		if (nLen <= 0)
		{
			cout << "socket = " << cSock << " 与服务器断开连接,任务结束 " << endl;
			return -1;
		}
		//将收取到的数据拷贝到消息缓冲区
		memcpy(_szMsgBuf+_lastPos, _szRecv, nLen);
		//消息缓冲区的数据尾部后移
		_lastPos += nLen;
		//判断消息缓冲区的数据长度大于消息头DataHeader长度
		while (_lastPos >= sizeof(DataHeader))
		{
			//这个时候就知道当前消息长度
			DataHeader *header = (DataHeader*)_szMsgBuf;
			//判断消息缓冲区的长度大于消息长度
			if (_lastPos >= header->dataLength)
			{
				//消息缓冲区剩余未处理数据的长度
				int nSize = _lastPos - header->dataLength;
				//处理网络消息
				OnNetMsg(header);
				//将消息缓冲区剩余未处理数据前移
				memcpy(_szMsgBuf, _szMsgBuf + header->dataLength, nSize);
				_lastPos = nSize;
			}
			else{
				//消息缓冲区剩余数据不够一条完整消息
				break;
			}
		}
		return 0;
	}

	//响应网络消息
	virtual void OnNetMsg(DataHeader *header)
	{
		switch (header->cmd)
		{
		case CMD_LOGIN_RESULT:
		{
								 LoginResult *login = (LoginResult*)header;
								 cout << "收到服务端消息:CMD_LOGIN_RESULT,数据长度:" << login->dataLength << endl;
		}
			break;
		case CMD_LOGOUT_RESULT:
		{
								  LogoutResult *logout = (LogoutResult*)header;
								  cout <<_sock<< "收到服务端消息:CMD_LOGOUT_RESULT,数据长度:" << logout->dataLength << endl;
		}
			break;
		case CMD_NEW_USER_JOIN:
		{
								  NewUserJoin *userJoin = (NewUserJoin*)header;
								  cout << _sock<< "收到服务端消息:CMD_NEW_USER_JOIN,数据长度:" << userJoin->dataLength << endl;
		}
			break;
		case CMD_ERROR:
		{
								  cout << _sock << "收到服务端消息:CMD_ERROR,数据长度:" << header->dataLength << endl;
		}
			break;
		default:
		{
				   cout << _sock << "收到未定义消息,数据长度为" << header->dataLength << endl;
		}
			break;
		}
	}

	//发送数据
	int SendData(DataHeader *header)
	{
		if (isRun() && header)
		{
			send(_sock, (const char *)header, header->dataLength, 0);
		}
		return SOCKET_ERROR;
	}
};

#endif
  • MessageHeader.hpp

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,      //新的用户加入
	CMD_ERROR,       //错误
};

struct DataHeader
{
	DataHeader()
	{
		dataLength = sizeof(DataHeader);
		cmd = CMD_ERROR;
	}
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
	char data[932];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
	char data[992];
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock = 0;
	}
	int sock;
};
  • client.cpp
#include "EasyTcpClient.hpp"
#include<thread>

bool g_bRun = true;

void cmdThread()
{
	while (1)
	{
		char cmdBuf[256] = {};
		
		cin >> cmdBuf;
		if (strcmp(cmdBuf, "exit") == 0){
			g_bRun = false;
			cout << "退出子线程" << endl;
			return;
		}
		else{
			cout << "不支持的命令" << endl;
		}
	}
}

int main()
{
	const int cCount = 10;
	EasyTcpClient* client[cCount];
	for (int i = 0; i < cCount; i++)
	{
		client[i] = new EasyTcpClient();
		client[i]->Connect((char *)"127.0.0.1", 4567); //192.168.247.128
	}
	//client.InitSocket();  不用定义,在连接时已经封装了定义

	//启动UI线程
	thread t1(cmdThread);         //启动线程函数
	t1.detach();

	Login login;
	strcpy(login.userName, "lyd");
	strcpy(login.PassWord, "lydmm");
	while (g_bRun)
	{
		for (int i = 0; i < cCount; i++)
		{

			//cout << "0" << endl;
			//client[i]->OnRun();
			//cout << "1"<<endl;
			client[i]->SendData(&login);
		}
	}
	for (int i = 0; i < cCount; i++)
	{
		client[i]->Close();
	}
	cout << "已退出" << endl;
	system("pause");
	return 0;
}

Second, solve the problem of server sticking and subcontracting

Server-side code:

  • EasyTcpServer.hpp
#ifndef _EasyTcpServer_hpp_
#define _EasyTcpServer_hpp_

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
#include<Winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET       (SOCKET)(~0)
#define SOCKET_ERROR                 (-1)
#endif

#include<iostream>
#include"MessageHeader.hpp"
#include<vector>
using namespace std;


//缓冲区最小单元大小
#ifndef RECV_BUFF_SIZE
#define RECV_BUFF_SIZE 10240
#endif

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 _sockfd;
	//第二缓冲区,消息缓冲区
	char _szMsgBuf[RECV_BUFF_SIZE * 10];
	//消息缓冲区的数据尾部位置
	int _lastPos = 0;
};


class EasyTcpServer
{
private:
	SOCKET _sock;
	vector<ClientSocket*> _clients;

public:
	EasyTcpServer()
	{
		_sock = INVALID_SOCKET;
	}

	virtual ~EasyTcpServer()
	{
		Close();
	}

	//初始化Socket
	SOCKET InitSocket()
	{
#ifdef _WIN32
		//启动Windows socket 2.x环境
		WORD ver = MAKEWORD(2, 2);
		WSADATA dat;
		WSAStartup(ver, &dat);
#endif
		//1,用Socket API建立建立TCP客户端
		if (_sock != INVALID_SOCKET)
		{
			cout << "_sock=" << (int)_sock << "关闭旧连接" << endl;
			Close();
		}
		_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		if (_sock == INVALID_SOCKET)
		{
			cout << "建立socket失败" << endl;
		}
		else
		{
			cout << (int)_sock << " 建立socket成功" << endl;
		}
		return _sock;
	}

	//绑定IP和端口号
	int Bind(const char *ip, unsigned short port)
	{
		//if (_sock != INVALID_SOCKET)
		//{
		//    InitSocket();
		//}
		//2,bind 绑定用于接受客户端连接的网络接口
		sockaddr_in _sin = {};
		_sin.sin_family = AF_INET;
		_sin.sin_port = htons(port);
#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


		int ret = ::bind(_sock, (sockaddr*)&_sin, sizeof(_sin));
		if (SOCKET_ERROR == ret)
		{
			cout << (int)_sock << "错误,绑定网络端口失败" << endl;
		}
		else
		{
			cout << "绑定网络端口成功" << port << endl;
		}
		return ret;
	}

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

	//接受客户端连接
	SOCKET Accept()
	{
		//4,accept 等待客户端连接
		sockaddr_in clientAddr = {};
		int nAddrlen = sizeof(clientAddr);
		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)
		{
			cout << (int)_sock << " 错误,接受到无效的客户端连接" << endl;
		}
		else
		{
			NewUserJoin userJoin;
			SendDataToAll(&userJoin);
			_clients.push_back(new ClientSocket(cSock));
			cout << "新的客户端加入:" << (int)cSock << "        ";
			cout << inet_ntoa(clientAddr.sin_addr) << endl;
		}
		return _sock;
	}

	//关闭Socket
	void Close()
	{
		if (_sock != INVALID_SOCKET)
		{
#ifdef _WIN32
			for (int n = (int)_clients.size() - 1; n >= 0; n--)
			{
				closesocket(_clients[n]->sockfd());
				delete _clients[n];
			}

			//8,关闭套接字closesocket
			closesocket(_sock);
			//清除Windows socket环境
			WSACleanup();
#else
			for (int n = (int)_clients.size() - 1; n >= 0; n--)
			{
				close(_clients[n]->sockfd());
				delete _clients[n];
			}
			//8,关闭套接字closesocket
			close(_sock);
#endif
			_clients.clear();
		}
	}

	//处理网络消息
	bool OnRun()
	{
		if (isRun())
		{
			//伯克利套接字
			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);


			SOCKET maxSock = _sock;
			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

			//添加非阻塞
			timeval t = { 1, 0 };

			int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);

			if (ret < 0)
			{
				cout << "select任务结束" << endl;
				Close();
				return false;
			}

			if (FD_ISSET(_sock, &fdRead))
			{
				FD_CLR(_sock, &fdRead);
				Accept();
			}
			for (int n = (int)_clients.size() - 1; n >= 0; n--)
			{
				if (FD_ISSET(_clients[n]->sockfd(), &fdRead))
				{
					if (RecvData(_clients[n]) == -1)
					{
						auto iter = _clients.begin();
						if (iter != _clients.end())
						{
							_clients.erase(iter);
							delete _clients[n];
						}
					}
				}
			}
			return true;
		}
		return false;
	}

	//是否工作中
	bool isRun()
	{
		return _sock != INVALID_SOCKET;
	}

	//缓冲区
	char _szRecv[RECV_BUFF_SIZE];// = {};
	//接收数据 处理粘包哦拆分包
	int RecvData(ClientSocket* pClient)
	{
		//5,接受客户端的请求数据
		int nLen = (int)recv(pClient->sockfd(), (char*)&_szRecv, RECV_BUFF_SIZE, 0);
		if (nLen <= 0)
		{
			cout << "客户端已经退出,任务结束" << endl;
			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->sockfd(), header);
				//将消息缓冲区剩余未处理数据前移
				memcpy(pClient->msgBuf(), pClient->msgBuf() + header->dataLength, nSize);
				pClient->setLastPos(nSize);
			}
			else{
				//消息缓冲区剩余数据不够一条完整消息
				break;
			}
		}
		return 0;
	}

	//响应网络消息
	virtual void OnNetMsg(SOCKET _cSock, DataHeader *header)
	{
		switch (header->cmd)
		{
		case CMD_LOGIN:
		{
						  //做数据偏移
						  Login *login = (Login*)header;
						  //cout << "收到命令:CMD_LOGIN,  数据长度:" << login->dataLength;
						  //cout << "  UserName:" << login->userName << "  PassWord:" << login->PassWord << endl;
						  //忽略判断用户名密码是否正确的过程
						  LoginResult ret;
						  //SendData(_cSock, (DataHeader *)&ret);
		}
			break;
		case CMD_LOGOUT:
		{
						   Login *logout = (Login*)header;
						   cout << "收到命令:CMD_LOGIN,  数据长度:" << logout->dataLength;
						   cout << "  UserName:" << logout->userName << endl;
						   //忽略判断用户名密码是否正确的过程
						   LogoutResult ret;
						   SendData(_cSock, (DataHeader *)&ret);
		}
			break;
		default:
		{
				   DataHeader ret;
				   SendData(_cSock, (DataHeader *)&ret);
		}
			break;
		}
	}

	//发送指定Socket数据
	int SendData(SOCKET _cSock, DataHeader *header)
	{
		if (isRun() && header)
		{
			return (int)send(_cSock, (const char *)header, header->dataLength, 0);
		}
		return SOCKET_ERROR;
	}

	//群发消息
	void SendDataToAll(DataHeader *header)
	{
		for (int n = (int)_clients.size() - 1; n >= 0; n--)
		{
			SendData(_clients[n]->sockfd(), header);
		}
	}
};

#endif
  • MessageHeader.hpp
enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,      //新的用户加入
	CMD_ERROR,       //错误
};

struct DataHeader
{
	DataHeader()
	{
		dataLength = sizeof(DataHeader);
		cmd = CMD_ERROR;
	}
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
	char data[932];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
	char data[992];
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock = 0;
	}
	int sock;
};
  • server.cpp
#include"EasyTcpServer.hpp"
#include<thread>

bool g_bRun = true;

void cmdThread()
{
	while (1)
	{
		char cmdBuf[256] = {};

		cin >> cmdBuf;
		if (strcmp(cmdBuf, "exit") == 0){
			g_bRun = false;
			cout << "退出子线程" << endl;
			return;
		}
		else{
			cout << "不支持的命令" << endl;
		}
	}
}
int main()
{
	EasyTcpServer server;
	server.InitSocket();
	server.Bind(nullptr, 4567);
	server.Listen(5);

	thread t1(cmdThread);         //启动线程函数
	t1.detach();

	while (g_bRun)
	{
		server.OnRun();
		//cout<<空闲处理其他业务<<endl;
	}
	server.Close();
	cout << "已退出" << endl;
	system("pause");
	return 0;
}

Third, break through the limitation of 64 connections under windows, and add high-precision timer function

Break through the select64 limitation under Windows:

#define FD_SETSIZE 1024

Client code:

  • EasyTcpClient.hpp
#ifndef _EasyTcpClient_hpp_
#define _EasyTcpClient_hpp_

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
#include<Winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET       (SOCKET)(~0)
#define SOCKET_ERROR                 (-1)
#endif

#include<iostream>
#include "MessageHeader.hpp"

using namespace std;

class EasyTcpClient
{
	SOCKET _sock;
public:
	EasyTcpClient()
	{
		_sock == INVALID_SOCKET;
	}

	virtual ~EasyTcpClient()
	{
		Close();
	}

	//初始化socket
	void InitSocket()
	{
#ifdef _WIN32
		//启动Windows socket 2.x环境
		WORD ver = MAKEWORD(2, 2);
		WSADATA dat;
		WSAStartup(ver, &dat);
#endif
		//1,用Socket API建立建立TCP客户端
		if (_sock == INVALID_SOCKET)
		{
			cout << "_sock=" << _sock << "关闭旧连接" << endl;
			Close();
		}
		_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		if (_sock == INVALID_SOCKET)
		{
			cout << "建立socket失败" << endl;
		}
		else
		{
			cout << "建立socket成功" << endl;
		}
	}

	//连接服务器
	int Connect(char *ip, unsigned short port)
	{
		if (_sock != INVALID_SOCKET)
		{
			InitSocket();
		}
		//2,连接服务器 connect
		sockaddr_in _sin = {};
		_sin.sin_family = AF_INET;
		_sin.sin_port = htons(4567);

#ifdef _WIN32
		_sin.sin_addr.S_un.S_addr = inet_addr(ip);
#else
		_sin.sin_addr.s_addr = inet_addr(ip);
#endif
		int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
		if (SOCKET_ERROR == ret)
		{
			cout << "错误,连接Socket失败" << endl;
		}
		else
		{
			cout << _sock << "  连接Socket" << ip << "成功" << endl;
		}
		return 0;
	}

	//7,关闭套接字closesocket
	void Close()
	{
		if (_sock != INVALID_SOCKET)
		{
#ifdef _WIN32
			closesocket(_sock);
			//清除Windows socket环境
			WSACleanup();
#else
			close(_sock);
#endif
		}
	}

	//处理网络消息
	bool OnRun()
	{
		if (isRun())
		{
			fd_set fdReads;
			FD_ZERO(&fdReads);
			FD_SET(_sock, &fdReads);

			//添加非阻塞
			timeval t = { 1, 0 };

			int ret = select(_sock + 1, &fdReads, 0, 0, NULL);
			if (ret < 0)
			{
				cout << "_sock=" << _sock << " 任务结束1" << endl;
				return false;
			}
			if (FD_ISSET(_sock, &fdReads))
			{
				FD_CLR(_sock, &fdReads);

				if (-1 == RecvData(_sock))
				{
					cout << "_sock=" << _sock << " 任务结束2" << endl;
					return false;
				}
			}
			return true;
		}
		return false;
	}

	//是否工作中
	bool isRun()
	{
		return _sock != INVALID_SOCKET;
	}

//缓冲区最小单元大小
#ifndef RECV_BUFF_SIZE
#define RECV_BUFF_SIZE 10240
#endif

	//接收缓冲区
	char _szRecv[RECV_BUFF_SIZE];
	//第二缓冲区,消息缓冲区
	char _szMsgBuf[RECV_BUFF_SIZE*10];
	//消息缓冲区的数据尾部位置
	int _lastPos = 0;
	//接收数据 处理粘包,拆分包
	int RecvData(SOCKET cSock)
	{
		//接收消息
		int nLen = (int)recv(cSock, _szRecv, RECV_BUFF_SIZE, 0);
		//接收消息为零
		if (nLen <= 0)
		{
			cout << "socket = " << cSock << " 与服务器断开连接,任务结束 " << endl;
			return -1;
		}
		//将收取到的数据拷贝到消息缓冲区
		memcpy(_szMsgBuf+_lastPos, _szRecv, nLen);
		//消息缓冲区的数据尾部后移
		_lastPos += nLen;
		//判断消息缓冲区的数据长度大于消息头DataHeader长度
		while (_lastPos >= sizeof(DataHeader))
		{
			//这个时候就知道当前消息长度
			DataHeader *header = (DataHeader*)_szMsgBuf;
			//判断消息缓冲区的长度大于消息长度
			if (_lastPos >= header->dataLength)
			{
				//消息缓冲区剩余未处理数据的长度
				int nSize = _lastPos - header->dataLength;
				//处理网络消息
				OnNetMsg(header);
				//将消息缓冲区剩余未处理数据前移
				memcpy(_szMsgBuf, _szMsgBuf + header->dataLength, nSize);
				_lastPos = nSize;
			}
			else{
				//消息缓冲区剩余数据不够一条完整消息
				break;
			}
		}
		return 0;
	}

	//响应网络消息
	virtual void OnNetMsg(DataHeader *header)
	{
		switch (header->cmd)
		{
		case CMD_LOGIN_RESULT:
		{
								 LoginResult *login = (LoginResult*)header;
								 //cout << "收到服务端消息:CMD_LOGIN_RESULT,数据长度:" << login->dataLength << endl;
		}
			break;
		case CMD_LOGOUT_RESULT:
		{
								  LogoutResult *logout = (LogoutResult*)header;
								  cout <<_sock<< "收到服务端消息:CMD_LOGOUT_RESULT,数据长度:" << logout->dataLength << endl;
		}
			break;
		case CMD_NEW_USER_JOIN:
		{
								  NewUserJoin *userJoin = (NewUserJoin*)header;
								  cout << _sock<< "收到服务端消息:CMD_NEW_USER_JOIN,数据长度:" << userJoin->dataLength << endl;
		}
			break;
		case CMD_ERROR:
		{
								  cout << _sock << "收到服务端消息:CMD_ERROR,数据长度:" << header->dataLength << endl;
		}
			break;
		default:
		{
				   cout << _sock << "收到未定义消息,数据长度为" << header->dataLength << endl;
		}
			break;
		}
	}

	//发送数据
	int SendData(DataHeader *header)
	{
		if (isRun() && header)
		{
			send(_sock, (const char *)header, header->dataLength, 0);
		}
		return SOCKET_ERROR;
	}
};

#endif
  • MessageHeader.hpp

enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,      //新的用户加入
	CMD_ERROR,       //错误
};

struct DataHeader
{
	DataHeader()
	{
		dataLength = sizeof(DataHeader);
		cmd = CMD_ERROR;
	}
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
	//char data[932];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
	//char data[992];
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock = 0;
	}
	int sock;
};
  • client.cpp
#include "EasyTcpClient.hpp"
#include<thread>

bool g_bRun = true;

void cmdThread()//(EasyTcpClient* client)
{
	while (1)
	{
		char cmdBuf[256] = {};
		
		//strcpy(cmdBuf,"login");
		cin >> cmdBuf;
		//if (strcmp(cmdBuf, "login") == 0){
		//	//线程thread;
		//	Login login;
		//	strcpy(login.userName, "lyd");
		//	strcpy(login.PassWord, "lydmima");
		//	client->SendData(&login);
		//}
		if (strcmp(cmdBuf, "exit") == 0){
			g_bRun = false;
			cout << "退出子线程" << endl;
			return;
		}
		else{
			cout << "不支持的命令" << endl;
		}
	}
}

int main()
{
	//EasyTcpClient client;
	//client.Connect((char *)"127.0.0.1", 4567);
	//启动UI线程
	//thread t1(cmdThread, &client);         //启动线程函数
	//t1.detach();

	//while (g_bRun)
	//{
		//client.OnRun();
	//}

	//client.Close();

//-----------------------------------------------------------------

	const int cCount = 100;
	EasyTcpClient* client[cCount];
	for (int i = 0; i < cCount; i++)
	{
		if (g_bRun)
		{
			client[i] = new EasyTcpClient();
		}
	}
	for (int i = 0; i < cCount; i++)
	{
		if (g_bRun)
		{
			client[i]->Connect((char *)"127.0.0.1", 4567); //192.168.247.128
		}   
	}

	//启动UI线程
 	thread t1(cmdThread);         //启动线程函数
	t1.detach();

	Login login;
	strcpy(login.userName, "lyd");
	strcpy(login.PassWord, "lydmm");

	while (g_bRun)
	{
		for (int i = 0; i < cCount; i++)
		{
			client[i]->SendData(&login);
			//if (!client[i]->OnRun())
			//{
			//	return 0;
			//}
		}
	}
	for (int i = 0; i < cCount; i++)
	{
		client[i]->Close();
	}
	cout << "已退出" << endl;
	system("pause");
	return 0;
}

Server-side code:

  • CELLTimestamp.hpp
#ifndef CELLTimestamp_hpp_
#define CELLTimestamp_hpp_

#pragma once
#include<chrono>
using namespace std::chrono;

class CELLTimestamp
{
public:
	CELLTimestamp()
	{
		update();
	}
	~CELLTimestamp()
	{

	}

	void update()
	{
		_begin = high_resolution_clock::now();
	}

	//获取当前秒
	long double getElapsedSecond()
	{
		return this->getElapsedTimeInMicroSec() * 0.000001;
	}
	//获取毫秒
	long double getElapsedTimeInMilliSec()
	{
		return this->getElapsedTimeInMicroSec() * 0.001;
	}
	//获取微秒
	long long getElapsedTimeInMicroSec()
	{
		return duration_cast<microseconds>(high_resolution_clock::now() - _begin).count();
	}
protected:
	time_point<high_resolution_clock> _begin;
};

#endif
  • EasyTcpServer.hpp
#ifndef _EasyTcpServer_hpp_
#define _EasyTcpServer_hpp_

#ifdef _WIN32
#define FD_SETSIZE 1024               
#define WIN32_LEAN_AND_MEAN
#include<windows.h>
#include<Winsock2.h>
#pragma comment(lib,"ws2_32.lib")
#else
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>

#define SOCKET int
#define INVALID_SOCKET       (SOCKET)(~0)
#define SOCKET_ERROR                 (-1)
#endif

#include<iostream>
#include"MessageHeader.hpp"
#include<vector>
#include"CELLTimestamp.hpp"
using namespace std;

int num_count = 1;

//缓冲区最小单元大小
#ifndef RECV_BUFF_SIZE
#define RECV_BUFF_SIZE 10240
#endif

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 _sockfd;
	//第二缓冲区,消息缓冲区
	char _szMsgBuf[RECV_BUFF_SIZE * 10];
	//消息缓冲区的数据尾部位置
	int _lastPos = 0;
};


class EasyTcpServer
{
private:
	SOCKET _sock;
	vector<ClientSocket*> _clients;
	CELLTimestamp _tTime;
	int _recvCount;

public:
	EasyTcpServer()
	{
		_sock = INVALID_SOCKET;
		_recvCount = 0;
	}

	virtual ~EasyTcpServer()
	{
		Close();
	}

	//初始化Socket
	SOCKET InitSocket()
	{
#ifdef _WIN32
		//启动Windows socket 2.x环境
		WORD ver = MAKEWORD(2, 2);
		WSADATA dat;
		WSAStartup(ver, &dat);
#endif
		//1,用Socket API建立建立TCP客户端
		if (_sock != INVALID_SOCKET)
		{
			cout << "_sock=" << (int)_sock << "关闭旧连接" << endl;
			Close();
		}
		_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

		if (_sock == INVALID_SOCKET)
		{
			cout << "建立socket失败" << endl;
		}
		else
		{
			cout << (int)_sock << " 建立socket成功" << endl;
		}
		return _sock;
	}

	//绑定IP和端口号
	int Bind(const char *ip, unsigned short port)
	{
		//if (_sock != INVALID_SOCKET)
		//{
		//    InitSocket();
		//}
		//2,bind 绑定用于接受客户端连接的网络接口
		sockaddr_in _sin = {};
		_sin.sin_family = AF_INET;
		_sin.sin_port = htons(port);
#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


		int ret = ::bind(_sock, (sockaddr*)&_sin, sizeof(_sin));
		if (SOCKET_ERROR == ret)
		{
			cout << (int)_sock << "错误,绑定网络端口失败" << endl;
		}
		else
		{
			cout << "绑定网络端口成功" << port << endl;
		}
		return ret;
	}

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

	//接受客户端连接
	SOCKET Accept()
	{
		//4,accept 等待客户端连接
		sockaddr_in clientAddr = {};
		int nAddrlen = sizeof(clientAddr);
		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)
		{
			cout << (int)_sock << " 错误,接受到无效的客户端连接" << endl;
		}
		else
		{
			NewUserJoin userJoin;
			SendDataToAll(&userJoin);
			_clients.push_back(new ClientSocket(cSock));
			cout <<num_count<< "  新的客户端加入:" << (int)cSock << "        ";
			num_count++;
			cout << inet_ntoa(clientAddr.sin_addr) << endl;
		}
		return _sock;
	}

	//关闭Socket
	void Close()
	{
		if (_sock != INVALID_SOCKET)
		{
#ifdef _WIN32
			for (int n = (int)_clients.size() - 1; n >= 0; n--)
			{
				closesocket(_clients[n]->sockfd());
				delete _clients[n];
			}

			//8,关闭套接字closesocket
			closesocket(_sock);
			//清除Windows socket环境
			WSACleanup();
#else
			for (int n = (int)_clients.size() - 1; n >= 0; n--)
			{
				close(_clients[n]->sockfd());
				delete _clients[n];
			}
			//8,关闭套接字closesocket
			close(_sock);
#endif
			_clients.clear();
		}
	}

	//处理网络消息
	bool OnRun()
	{
		if (isRun())
		{
			//伯克利套接字
			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);


			SOCKET maxSock = _sock;
			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

			//添加非阻塞
			timeval t = { 1, 0 };

			int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExp, &t);

			if (ret < 0)
			{
				cout << "select任务结束" << endl;
				Close();
				return false;
			}

			if (FD_ISSET(_sock, &fdRead))
			{
				FD_CLR(_sock, &fdRead);
				Accept();
			}
			for (int n = (int)_clients.size() - 1; n >= 0; n--)
			{
				if (FD_ISSET(_clients[n]->sockfd(), &fdRead))
				{
					if (RecvData(_clients[n]) == -1)
					{
						auto iter = _clients.begin();
						if (iter != _clients.end())
						{
							_clients.erase(iter);
							delete _clients[n];
						}
					}
				}
			}
			return true;
		}
		return false;
	}

	//是否工作中
	bool isRun()
	{
		return _sock != INVALID_SOCKET;
	}

	//缓冲区
	char _szRecv[RECV_BUFF_SIZE];// = {};
	//接收数据 处理粘包哦拆分包
	int RecvData(ClientSocket* pClient)
	{
		//5,接受客户端的请求数据
		int nLen = (int)recv(pClient->sockfd(), (char*)&_szRecv, RECV_BUFF_SIZE, 0);
		if (nLen <= 0)
		{
			cout << "客户端已经退出,任务结束" << endl;
			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->sockfd(), header);
				//将消息缓冲区剩余未处理数据前移
				memcpy(pClient->msgBuf(), pClient->msgBuf() + header->dataLength, nSize);
				pClient->setLastPos(nSize);
			}
			else{
				//消息缓冲区剩余数据不够一条完整消息
				break;
			}
		}
		return 0;
	}

	//响应网络消息
	virtual void OnNetMsg(SOCKET _cSock, DataHeader *header)
	{
		_recvCount++;
		auto t1 = _tTime.getElapsedSecond();
		if (_tTime.getElapsedSecond() >= 1.0)
		{
			cout << "tTime	" << t1 <<"	client	" <<_clients.size()<<"	socket	" << _sock << "	_recvCount	" << _recvCount << endl;
			_recvCount = 0;
			_tTime.update();
		}
		switch (header->cmd)
		{
		case CMD_LOGIN:
		{
						  //做数据偏移
						  Login *login = (Login*)header;
						  //cout << "收到命令:CMD_LOGIN,  数据长度:" << login->dataLength;
						  //cout << "  UserName:" << login->userName << "  PassWord:" << login->PassWord << endl;
						  //忽略判断用户名密码是否正确的过程
						  LoginResult ret;
						  //SendData(_cSock, (DataHeader *)&ret);
		}
			break;
		case CMD_LOGOUT:
		{
						   Login *logout = (Login*)header;
						   cout << "收到命令:CMD_LOGIN,  数据长度:" << logout->dataLength;
						   cout << "  UserName:" << logout->userName << endl;
						   //忽略判断用户名密码是否正确的过程
						   LogoutResult ret;
						   SendData(_cSock, (DataHeader *)&ret);
		}
			break;
		default:
		{
				   DataHeader ret;
				   SendData(_cSock, (DataHeader *)&ret);
		}
			break;
		}
	}

	//发送指定Socket数据
	int SendData(SOCKET _cSock, DataHeader *header)
	{
		if (isRun() && header)
		{
			return (int)send(_cSock, (const char *)header, header->dataLength, 0);
		}
		return SOCKET_ERROR;
	}

	//群发消息
	void SendDataToAll(DataHeader *header)
	{
		for (int n = (int)_clients.size() - 1; n >= 0; n--)
		{
			SendData(_clients[n]->sockfd(), header);
		}
	}
};

#endif
  • MessageHeader.hpp
enum CMD
{
	CMD_LOGIN,       //登入
	CMD_LOGIN_RESULT,
	CMD_LOGOUT,      //登出
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,      //新的用户加入
	CMD_ERROR,       //错误
};

struct DataHeader
{
	DataHeader()
	{
		dataLength = sizeof(DataHeader);
		cmd = CMD_ERROR;
	}
	short dataLength;
	short cmd;
};

//匹配四个消息结构体
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char PassWord[32];
	//char data[932];
};

struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 0;
	}
	int result;
	//char data[992];
};

struct Logout : public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGOUT;
	}
	char userName[32];
};

struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};

struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock = 0;
	}
	int sock;
};
  • server.cpp
#include"EasyTcpServer.hpp"
#include<thread>

int main()
{
	EasyTcpServer server;
	server.InitSocket();
	server.Bind(nullptr, 4567);
	server.Listen(5);

	while (server.isRun())
	{
		server.OnRun();
	}
	server.Close();
	cout << "已退出" << endl;
	system("pause");
	return 0;
}

Guess you like

Origin blog.csdn.net/qq_46423166/article/details/111188752