游戏服务器之服务器之间的被动连接

本文探讨了游戏服务器中如何实现服务器之间的被动连接,通过自定义的socket线程启动、接收连接线程以及数据收发线程进行详细阐述。启动包括连接接受线程和数据处理线程,接收线程负责新建客户端连接,数据收发线程则包含性能统计和数据收发限制,确保高效稳定的数据交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务器之间的被动连接的设计和实现,本文的代码内容是以自定义被动连接socket来体现。

功能是服务器之间的数据收发的被动连接的处理。

应用如:数据服务器的数据服务器对象是继承于自定义被动连接socket的,处理逻辑服务器的连接活动。


设计上:

启动两个线程:

(1)接收连接线程(增加活动会话)

(2)启动数据收发线程:

处理所有的会话上的数据,接收数据并拷贝到缓存,和处理发送队列数据发送 。

 

1、自定义服务器socket线程启动

创建用于接受连接的套接字,绑定套接字到地址和端口,监听端口

启动线程:
1)连接接受线程
2)启动数据处理线程

BOOL CCustomServerSocket::Startup()
{
	int nError;
	if ( TRUE == InterlockedCompareExchange(&m_boStoped, FALSE, TRUE) )//设置线程启动标识
	{
		//创建用于接受连接的套接字
		nError = createSocket(&m_nSocket);
		if ( nError )
		{
			logError("创建%s服务套接字失败,%d", m_sServiceName,nError);
			return FALSE;
		}
		//绑定套接字到地址和端口
		nError = bind(m_sBindHost, (INT)m_nBindPort);
		if ( nError )
		{
			logError("绑定%s服务器套接字到%s:%d失败,Error %d", m_sServiceName, m_sBindHost, m_nBindPort,nError);
			return FALSE;
		}
		//监听
		nError = listen();
		if ( nError )
		{
			logError("无法在%s:%d端口监听%s服务,Error %d", m_sBindHost, m_nBindPort, m_sServiceName,nError);
			return FALSE;
		}
		//调用启动函数(启动前可做一些操作)
		if ( !DoStartup() )
		{
			return FALSE;
		}
		//启动接受连接的线程
		m_boAcceptThreadStoped = FALSE;
		#ifdef WINDOWS
		m_hAcceptThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ServerSocketAcceptThreadRoutine, this, 0, NULL);
		if ( !m_hAcceptThread )
		{
			logError("创建%s服务器连接接受线程失败,error %d", m_sServiceName,GetLastError());
			return FALSE;
		}
		//启动数据处理线程
		m_hDataThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ServerSocketDataThreadRoutine, this, 0, NULL);
		if ( !m_hDataThread )
		{
			logError("创建%s服务器数据处理线程失败,error %d", m_sServiceName,GetLastError());
			return FALSE;
		}
		#else
		//连接接受线程
		if(0 != pthread_create(&m_hAcceptThread, 0, (LPTHREAD_START_ROUTINE)(ServerSocketAcceptThreadRoutine), this))
		{
			logError("创建%s服务器连接接受线程失败,error %d", m_sServiceName,errno);
			return FALSE;
		}
		//启动数据处理线程
		if(0 != pthread_create(&m_hDataThread, 0, (LPTHREAD_START_ROUTINE)(ServerSocketDataThreadRoutine), this))
		{
			logError("创建%s服务器数据处理线程失败,error %d", m_sServiceName,errno);
			return FALSE;
		}
		#endif
	}
	return TRUE;
}



2、接收连接线程

  不断循环接收连接,并创建客户端连接对象,设置连接收发缓冲区,并添加到客户端连接对象列表

VOID CCustomServerSocket::ServerSocketAcceptThreadRoutine(CCustomServerSocket *lpServer)
{
	int nError;
	SOCKET nSock;
	CCustomServerClientSocket *pClient;
	SOCKADDR_IN addr_In;
	
	while ( !lpServer->m_boStoped )
	{
		nError = lpServer->accept(&nSock, 3000, &addr_In);
		//成功
		if ( nError == 0 )
		{
			//创建客户端连接对象
			pClient = lpServer->CreateClientSocket(nSock, &addr_In);
			//创建连接对象失败,则关闭套接字
			if ( !pClient )
			{
				closesocket(nSock);
				continue;
			}
			//设置为非阻塞模式
			nError = pClient->setBlockMode(false);
			if ( nError )
			{
				logError("无法将%s套接字接受的连接设置为非阻塞模式,%d", lpServer->m_sServiceName,nError);
				delete pClient;
				continue;
			}
			//调整接收缓冲区大小(32k)
			nError = pClient->setRecvBufSize(32 * 1024);
			if ( nError )
			{
				//此操作的错误可以忽略
				logError("无法将%s套接字接受的连接调整接受缓冲区大小,%d", lpServer->m_sServiceName,nError);
			}
			//调整发送缓冲区大小(32k)
			nError = pClient->setSendBufSize(32 * 1024);
			if ( nError )
			{
				//此操作的错误可以忽略
				logError("无法将%s套接字接受的连接调整发送缓冲区大小,%d", lpServer->m_sServiceName,nError);
			}
			lpServer->OnClientConnect(pClient);
			if (pClient->connected())
				lpServer->m_ClientList.append(pClient);//添加到客户端连接列表
			else lpServer->DestroyClientSocket(pClient);
			
			moon::OS::osSleep(1);
			continue;
		}
		
		//超时
		if ( nError == SOCKET_ERROR - 1 )
			continue;
		//接收连接出错
		logError("错误,%d",nError);
		moon::OS::osSleep(1000);
		break;
	}
	
	lpServer->m_boAcceptThreadStoped = TRUE;//标记接受连接的线程已经退出
	ExitThread(0);//设置线程退出返回值
}


3、数据收发线程

(1)性能统计和数据收发周期限制

不断执行socket线程例程,控制周期,并计算执行例程的性能统计。
周期控制:
1) 每次循环最多调用两次socket线程例程的函数(数据收发和维持心跳包),最大循环时间4ms(不足的时间会在当次循环末尾休眠该时间)
性能统计:
1)每隔60s重新统计一次
2)记录循环次数(最大和最小)
3)记录收发时间(最大和最小)
4)记录休眠时间(最大和最小)


VOID CCustomServerSocket::ServerSocketDataThreadRoutine(CCustomServerSocket *lpServer)
{
	TICKCOUNT dwStartTick, dwCurTick, dwRestaticTick = 0;
	int nLoopCount;
	DWORD dwSleepTick;
	
	lpServer->OnSocketDataThreadStart();
	
	while ( !lpServer->m_boAcceptThreadStoped )
	{
		lpServer->m_MainProcPerformance.dwTickBegin = dwStartTick = _getTickCount();
		//是否重新统计循环性能
		if ( dwStartTick >= dwRestaticTick )
		{
			dwRestaticTick = dwStartTick + 60 * 1000;
			lpServer->m_LoopPerformance.nMaxLoop = 0;
			lpServer->m_LoopPerformance.nMinLoop = 99999;
			
			lpServer->m_MainProcPerformance.dwMaxTick = 0;
			lpServer->m_MainProcPerformance.dwMinTick = 9999;
		}
		
		nLoopCount = 0;
		while ( TRUE )
		{
			lpServer->SingleRun();//服务端连接例程(处理连接的客户端)
			nLoopCount++;
			dwCurTick = _getTickCount();
			//连接数据处理线程的单次循环时间限制(单位是毫秒),默认值是4
			//最大统计循环时间是4ms
			if ( dwCurTick - dwStartTick >= lpServer->m_uLoopTimeLimit )
			{
				dwSleepTick = 1;
				break;
			}
			//连接数据处理线程的单次循环次数限制,默认值为2
			//最大统计循环次数是2
			if ( nLoopCount >= lpServer->m_nLoopCountLimit )
			{
				//每次循环时间是4ms,如果不够就休眠剩下的时间,否则就休眠1ms
				dwSleepTick = (DWORD)(lpServer->m_uLoopTimeLimit - (dwCurTick - dwStartTick));
				//如果实际休眠值为此值则说明上一步的运算溢出了
				if ( dwSleepTick > lpServer->m_uLoopTimeLimit + 2 )
				dwSleepTick = (DWORD)(lpServer->m_uLoopTimeLimit + 2);
				break;
			}
		}
		//循环次数
		lpServer->m_LoopPerformance.nLastLoop = nLoopCount;
		//数据收发处理时间
		lpServer->m_MainProcPerformance.dwLastTick = dwCurTick - lpServer->m_MainProcPerformance.dwTickBegin;
		//休眠时间
		lpServer->m_MainSleepPerformance.dwLastTick = dwSleepTick;
		//设置统计最大最小循环次数
		if ( lpServer->m_LoopPerformance.nMaxLoop < nLoopCount )
		{
			lpServer->m_LoopPerformance.nMaxLoop = nLoopCount;
		}
		if ( lpServer->m_LoopPerformance.nMinLoop > nLoopCount )
		{
			lpServer->m_LoopPerformance.nMinLoop = nLoopCount;
		}
		//设置统计最大最小时间
		if ( lpServer->m_MainProcPerformance.dwMaxTick < lpServer->m_MainProcPerformance.dwLastTick )
		{
			lpServer->m_MainProcPerformance.dwMaxTick = lpServer->m_MainProcPerformance.dwLastTick;
		}
		if ( lpServer->m_MainProcPerformance.dwMinTick > lpServer->m_MainProcPerformance.dwLastTick )
		{
			lpServer->m_MainProcPerformance.dwMinTick = lpServer->m_MainProcPerformance.dwLastTick;
		}
		//设置休眠的最大最小时间
		if ( lpServer->m_MainSleepPerformance.dwMaxTick < dwSleepTick )
		{
			lpServer->m_MainSleepPerformance.dwMaxTick = dwSleepTick;
		}
		if ( lpServer->m_MainSleepPerformance.dwMinTick > dwSleepTick )
		{
			lpServer->m_MainSleepPerformance.dwMinTick = dwSleepTick;
		}
		//休眠
		moon::OS::osSleep(dwSleepTick);
	}
	
	if ( lpServer->connected() )
	{
		lpServer->close();
	}
	
	lpServer->OnSocketDataThreadStop();
	//关闭所有连接
	lpServer->CloseAllClients();
	
	ExitThread(0);//设置线程退出返回值
}


(2)处理客户端连接

处理存活的客户端连接

VOID CCustomServerSocket::SingleRun()
{
//处理连接的客户端
ProcessClients();
//调用例行函数
OnRun();
}

处理所有存活的客户端连接(被动连接socket对象)
VOID CCustomServerSocket::ProcessClients()
{
	INT_PTR i;
	CCustomServerClientSocket *pClient;
	
	//循环处理每个客户端连接
	m_ClientList.flush();
	//必须降序循环,因为列表中的数据可能在循环中被移除
	for ( i=m_ClientList.count() - 1; i>-1; --i )
	{
		pClient = m_ClientList[i];
		//处理活动的连接
		pClient->Run();
		//删除断开的连接
		if ( !pClient->connected() )
		{
			m_ClientList.lock();
			m_ClientList.remove(i);
			m_ClientList.unlock();
			DestroyClientSocket(pClient);
			continue;
		}
	}
}


活的连接的处理

VOID CCustomServerClientSocket::SingleRun()
{
	super::SingleRun();
	
	//发送保持连接消息(服务器主动保持长连接)
	if ( m_boActiveKeepAlive && connected() )
	{
		if ( _getTickCount() - m_dwMsgTick >= 10 * 1000 )
		{
			SendKeepAlive();
		}
	}
}



socket数据接收和发送

VOID CCustomWorkSocket::SingleRun()
{
	//接收数据
	if ( connected() )
		ReadSocket();//接收数据到接收缓冲区
	
	//处理接受到的数据包
	if ( connected() )
	ProcessRecvBuffers(m_pProcRecvBuffer);//拷贝数据和粘包
	
	//调用例行函数
	OnRun();
	
	//发送数据
	if ( connected() && m_bSendData)
	{
		SendSocketBuffers();//发送发送队列中的数据包
	}
}


数据接收。接收数据到接收缓冲区。每次接受数据长度最大值限制4096字节,接受缓冲区大小不够时会增长4096*2字节大小。

VOID CCustomWorkSocket::ReadSocket()
{
	static const int OnceRecvSize = 4096;//每次接收的数据的最大值
	int nError;
	TICKCOUNT dwCurTick = _getTickCount();
	
	while ( TRUE )
	{
		//增长接收缓冲区大小
		if ( m_pRecvBuffer->nSize - m_pRecvBuffer->nOffset < OnceRecvSize * 2 )
		{
			size_t nPointer = m_pRecvBuffer->pPointer - m_pRecvBuffer->pBuffer;
			m_pRecvBuffer->nSize += OnceRecvSize * 2;
			m_pRecvBuffer->pBuffer = (char*)realloc(m_pRecvBuffer->pBuffer, m_pRecvBuffer->nSize);
			m_pRecvBuffer->pPointer = m_pRecvBuffer->pBuffer + nPointer;
		}
		//从套接字读取数据
		nError = recv(&m_pRecvBuffer->pBuffer[m_pRecvBuffer->nOffset], OnceRecvSize);
		if ( nError <= 0 )
			break;
		
		m_pRecvBuffer->nOffset += nError;
		m_pRecvBuffer->pBuffer[m_pRecvBuffer->nOffset] = 0;
		m_dwMsgTick = dwCurTick;
	}
}




在Delphi中,套接字(Socket)编程用到的基本类是TServerSocket与TClientSocket。这两个类全部位于ScktComp单元中。其类型定义如下: type TServerSocket = class (ScktComp.TCustomServerSocket); TClientSocket = class (ScktComp.TCustomSocket)。      在编写程序时,首先要对TServerSocket(在服务器端)与TClientSocket(在客户端)进行实例化。对于TServerSocket的对象,主要设置其服务类型(ServerType)与端口(Port)的属性,然后编写“OnClientRead”事件处理程序的代码,处理来自客户机的请求。如要启动服务器,设置TServerSocket对象的Active属性为真(即Active := True),如要停止服务器,则设置TServerSocket对象的Active属性为假(即Active := False)。      对于TClientSocket的对象,主要设置对方服务器的服务类型(ServerType)、端口(Port)以及IP地址(Address)的属性,之后编写“OnConnect与OnRead”事件处理程序的代码“OnConnect”事件处理程序用来检查与服务器连接成功与否(必须在这里进行检查才有效),“OnRead”事件处理程序用来读取服务器发来的信息。如要连接服务器,设置TClientSocket对象的Active属性为真(即Active := True;注意:检查连接是否成功,必须在“OnConnect”事件处理程序中进行),如要断开与服务器的连接,则设置TClientSocket对象的Active属性为假(即Active := False)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值