基于select网络I/o的简单实现

本代码实现了select跨平台
在我自己window10 ,I58500 8G内存本机测试下,windows 能稳定连接2000左右客户端,虚拟机ubuntu能稳定连接1200个客户端,数据一秒能达到500万左右。

#ifndef EASYTCPSERVER_H
#define EASYTCPSERVER_H
#include"Message.hpp"//协议头
#include"Time.hpp"//时间
#include<stdio.h>

#include
#include
#include
#include
#ifdef _WIN32
//修改windows的select数的限制
#define FD_SETSIZE 1000
#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#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 // _WIN32

#define MSG_BUF_SIZE 1024

//客户端数据类型
class ClientSocket
{
public:
ClientSocket(SOCKET sockfd = INVALID_SOCKET)
{
_sockfd = sockfd;
_lastPos = 0;
memset(_mMesgBuf, 0, MSG_BUF_SIZE);
}
~ClientSocket()
{

		if (INVALID_SOCKET != _sockfd)
		{

#ifdef _WIN32
closesocket(_sockfd);
//WSACleanup();
#else
close(_sockfd);
#endif // _WIN32
_sockfd = INVALID_SOCKET;
}
}
SOCKET getSocketFd()
{
return this->_sockfd;
}
char msgBuf()
{
return _mMesgBuf;
}
int getLastPos()
{
return _lastPos;
}
void setLastPost(int pos)
{
this->_lastPos = pos;
}
//接收数据
int RecveData()
{
int nlen = recv(this->_sockfd, _mMesgBuf + _lastPos, MSG_BUF_SIZE - _lastPos, 0);
if (nlen < 0)
{
printf(“客户端%d已断开\n”,_sockfd);
return -1;
}
// memcpy(MsgBuf + lastPos, RecBuf, nlen);//当每次收到的数据比较小时,需要合成一个大的数据包
_lastPos += nlen;
while (_lastPos >= sizeof(DataHeader))
{
//这时就可以知道当前消息的长度
DataHeader header = (DataHeader)_mMesgBuf;
if (_lastPos >= header->dataLength)//当数据收的比较多时,需要进行拆包
{
//当前消息取出后,剩余的数据长度
int nSize = _lastPos - header->dataLength;
//OnNetMsg(header);,服务收到客户端的数据后进行处理
memcpy(_mMesgBuf, _mMesgBuf + header->dataLength, nSize);
_lastPos = nSize;
}
else
{
break;
}
}
return nlen;
}
//发送数据
int SendData(DataHeader
header)
{
if (header)
{
return send(_sockfd, (const char*)header, header->dataLength, 0);
}
return SOCKET_ERROR;
}
friend class CellSever;
private:
SOCKET _sockfd;
//数据缓冲区
char _mMesgBuf[MSG_BUF_SIZE];
//数据缓冲区数据尾部位置
int _lastPos;
};

//网络事件接口
class INetEvent
{
public:
//纯虚函数
//客户端上线
virtual void OnNetJoin(ClientSocket* pClient) = 0;
//客户端下线
virtual void OnLeave(ClientSocket *pClient) = 0;
//客户端有消息
virtual void OnNetMsg(ClientSocket cSockk, DataHeader header) = 0;
//recv事件
virtual void OnNetRecv(ClientSocket
pClient, size_t size) = 0;
};

//实现服务器与客户端的交互,代理小服务器
class CellSever
{
public:
CellSever(SOCKET sock = INVALID_SOCKET)
{
_sock = sock;
_pThread = nullptr;
_pEvent = nullptr;
m_recCount = 0;
FD_ZERO(&fdRead);
maxSock = 0;
}

~CellSever()
{
	Close();
	_sock = INVALID_SOCKET;
}


bool isRun()
{
	return _sock != INVALID_SOCKET;
}
void setEventObj(INetEvent *event)
{
	this->_pEvent = event;
}
//服务器运行线程函数
//负责与已经客户端的数据交互
bool OnRun()
{
	while (isRun())
	{
		
		if (_clients.empty())
		{
			//没有客户端,先让出当前时间片
			std::chrono::milliseconds t(1);
			std::this_thread::sleep_for(t);
			continue;
		}
		
		//select 第一个参数,是表示描述符的范围 一般为所有文件描述符最大数加1,windows这个数可以写成1
		fd_set tempset = this->fdRead;
		timeval t = { 0,1 };
		int rec_size;
		int ret = select(maxSock+1, &tempset, nullptr, nullptr, &t);
		if (ret < 0)
		{
			//select出问题,关闭网络
			Close();
			return false;
		}
		else if (ret == 0)
		{
			//没有数据,让出当前时间片
			std::chrono::milliseconds t(1);
			std::this_thread::sleep_for(t);
		}
		else
		{
			//有数据来
			//=====-=-=-=-=-=-===-=-==-=-==-=-==-=-=-=-=-=-=-=-=-=-=-=-=-==-=-======-==-=-=-=
			auto ite = _clients.begin();
			auto end = _clients.end();
			for (; ite!= end; ++ite)
			{
				if (FD_ISSET((*ite)->_sockfd, &tempset))
				{
					rec_size = (*ite)->RecveData();
					if (-1==rec_size)
					{
						//客户端以退出
						FD_CLR((*ite)->_sockfd, &fdRead);//将客户端socket去除
						_pEvent->OnLeave((*ite));
						delete (*ite);
						_clients.erase(ite);
					}
					else
					{
						_pEvent->OnNetRecv((*ite), rec_size);
					}
					--ret;
					if (ret == 0)//已处理完当前阶段所有的客户端的数据请求 
						continue;
				}
			}
		}


	}
}

void Start()
{
	_pThread = new std::thread(std::mem_fun(&CellSever::OnRun), this);
}
size_t getClientCount()
{
	return _clients.size()/* + _client_buff.size()*/;
}
void addClient(ClientSocket *client)
{
	std::lock_guard<std::mutex> lock(_mutex);//加锁
	this->_clients.push_back(client);
	//将客户端放入select的可读数组中
	FD_SET(client->_sockfd, &fdRead);
	if (client->_sockfd > this->maxSock)
		this->maxSock = client->_sockfd;
}

private:
void Close()//关闭掉连接的客户端
{
//关闭线程
this->_sock = INVALID_SOCKET;
this->_pThread->join();//回收线程资源
for (size_t i = 0; i < _clients.size(); ++i)
delete _clients[i];
_clients.clear();
}
private:
SOCKET _sock;
//客户端队列
std::vector<ClientSocket*> _clients;
//缓冲客户端队列
//std::vector<ClientSocket*> _client_buff;
std::mutex _mutex;
std::thread *_pThread;
INetEvent *_pEvent;
//select模型使用参数
fd_set fdRead;
SOCKET maxSock;
public:
std::atomic_int m_recCount;
};

class EasyTcpServer :public INetEvent
{
public:
EasyTcpServer()
{
_sock = INVALID_SOCKET;
_recvCount = 0;
_clientCount = 0;
}
virtual ~EasyTcpServer()
{
Close();
}
//初始化SOCKET
SOCKET InitSocket()
{
#ifdef _WIN32
//加载库
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
#endif // _WIN32
if (INVALID_SOCKET != _sock)
{
Close();
}
_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (INVALID_SOCKET == _sock)
return false;
return true;
}
//绑定IP和端口号
int Bind(const char ip, unsigned short port)
{
sockaddr_in addr;
memset(&addr, 0, sizeof(sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
#ifdef _WIN32
if (ip)
{
addr.sin_addr.S_un.S_addr = inet_addr(ip);
}
else
{
addr.sin_addr.S_un.S_addr = INADDR_ANY;
}
#else
if (ip)
{
addr.sin_addr.s_addr = inet_addr(ip);
}
else
{
addr.sin_addr.s_addr = INADDR_ANY;
}
#endif // _WIN32
int len = sizeof(addr);
int ret= bind(_sock, (const sockaddr
)&addr, len);
return ret;
}
int Listen(int n)
{
return listen(_sock, n);
}
SOCKET Accept()
{
sockaddr_in clientadrr;
int len = sizeof(sockaddr_in);
SOCKET clientSocket = INVALID_SOCKET;
#ifdef _WIN32
clientSocket = accept(_sock, (sockaddr*)&clientadrr, &len);
#else
clientSocket = accept(_sock, (sockaddr*)&clientadrr,(socklen_t*) &len);
#endif // _WIN32
if (INVALID_SOCKET != clientSocket)
{
addClientToCellSever(clientSocket);
}
return clientSocket;
}
void Start(int numCellSever=1)
{
//先开始监听socket向select模型转换
FD_ZERO(&fdRead);
FD_SET(this->_sock, &fdRead);
for (size_t i = 0; i < numCellSever; ++i)
{
auto sever = new CellSever(this->_sock);
_cellServers.push_back(sever);
sever->setEventObj(this);
sever->Start();//启动线程进行数据处理
}
}
void Run()
{
while (IsRun())
{
fd_set temp = fdRead;
//timeval t{ 0,10 };
int ret = select(_sock + 1, &fdRead, nullptr, nullptr,nullptr);
if (ret < 0)
{
//select模型出了问题,服务器关闭
Close();
return;
}
else if (ret == 0)
{
//没有数据,让出当前时间片
std::chrono::milliseconds t(1);
std::this_thread::sleep_for(t);
}
else
{
//有客户端接入,进行来连接
Accept();
}
}
}
void timeOut()
{
auto t1 = _tTime.getElapsedSecond();
if (t1 >= 1.0)//每个一秒打印一下当前的数据连接情况
{
printf("<%d>个线程,客户端连接:<%d> \t ,数据并发量:<%d>\n", _cellServers.size(), _clientCount, _recvCount);
_recvCount = 0;
}
}
private:
void Close()
{
if (_sock != INVALID_SOCKET)
{
if (INVALID_SOCKET != _sock)
{
#ifdef _WIN32
closesocket(_sock);
WSACleanup();
#else
close(_sock);
#endif // _WIN32
for (size_t i = 0; i < _cellServers.size(); ++i)
{
delete _cellServers[i];
}
_sock = INVALID_SOCKET;
_cellServers.clear();
}
}
}
void addClientToCellSever(SOCKET clientSocket)
{
ClientSocket *client = new ClientSocket(clientSocket);
auto pMinSever = _cellServers[0];
for (auto pCellSever : _cellServers)//找出对应最小的代理服务器
{
if (pMinSever->getClientCount() > pCellSever->getClientCount())
pMinSever = pCellSever;
}
pMinSever->addClient(client);
OnNetJoin(client);
}
bool IsRun()
{
return _sock != INVALID_SOCKET;
}

//==========================//用于记录客户端的并发量
//客户端上线
void OnNetJoin(ClientSocket* pClient)
{
	++_clientCount;
}
//客户端下线
virtual void OnLeave(ClientSocket *pClient)
{
	--_clientCount;
	printf("%d断开连接\n", pClient->getSocketFd());
}
//客户端有消息
virtual void OnNetMsg(ClientSocket cSockk, DataHeader *header)
{

}
//recv事件
virtual void OnNetRecv(ClientSocket* pClient,size_t size)
{
	_recvCount += size;
}

private:
SOCKET _sock;
//std::vector<ClientSocket*> _clients;
std::vector<CellSever*> _cellServers;
CELLTimeStamp _tTime;
fd_set fdRead;

std::atomic<size_t> _recvCount;
std::atomic<size_t> _clientCount;

};
#endif // !EASYTCPSERVER_H

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值