`

系列二:游戏服务器的邮局路由

阅读更多
 /*
    QQ: 2#4#2#1#0#6#7#6#4    #表示为空
    Mail: lin_style#foxmail.com    #替换成@ 
*/
 

行为
难点

邮局路由流程图
邮局路由详细图
实例代码目录说明

算法的示例代码


行为

对于客户端:
1.    接受客户端的短连接
2.    返回给客户端一个密匙
对于邮局服务器:
1.    接受邮局服务器的主动连接并记录,理论上可以动态
2.    接受邮局服务器的定时更新
3.    筛选出邮局服务器的负载信息返回


难点
粗略思考下,有这么几个纠结的地方
1.    如何做到每次都是返回最小的服务器结构
2.    客户端每次请求都是并行的(udp线程根据CPU数量启动,可以视为一个资源竞争)
3.    服务器资源的更新会不会和第一条产生资源竞争
       当然,你也可以把这块做成单线程,直接进行一个排序后算出,也非常的简单。但是我的观点是,东西总是越做越极限,虽然简单不一定高效的方法可以解决,但是还有更简单更高效更新一层的东西来等你挖掘。当然,前提是有时间。
      设计的几个方案都无可避免的要发生资源竞争。最终采取的如下,非常的优雅,不管多少并行下都不带锁。其核心毫不夸张的说只有5行左右。那就是“概率”。
      举一个简单的例子,假设有4台机器,分别是400,300,200,100人。那么它们的比例就是4:3:2:1,那么在负载均衡的情况下,假设投入 1000人,那么每台机器分配到的人数应该是100,200,300,400人。如果我们事先分配好这些比例,并且给出一个按此比例的随机函数,是不是可以非常轻松的解决所谓的高并发锁的困扰呢?比如随机1000次的实验,在0的位置上出现100次,1的位置上出现200次。。。。当然可以!再设想一下,假如我们不要求每个服务器的负载都是非常的精确话,以下的伪码就可以表现出一个流程:

      接收到客户请求,fun 随机返回一个概率比例值,send
      接受到更新请求,fun 比例重新设置

注意,在“fun比例重新设置”函数中是不加锁的,每台服务器都有对应的维护对象;而“fun随便返回一个概率比率值”函数,虽然要根据服务器人数进行重新设置比例,但是这些精度的损耗可以忽略不计(更新人数时仅仅是一个赋值过程)。
      我给出的demo中采取的是rand函数(取值范围是0-32767)。关于这个函数的缺陷有如下:

  • 假如你求余10000的话,你会发现前面几千的概率非常高
  • 加入你求余1000的话,你会发现前面700多的概率非常高

       原因很简单,最大值不是你求余数的倍数。虽然这些可以忽略,但我还是做了处理。在进行比例计算的时候,最后一个值会有一些误差(就比如10/3这样的整取),当随机到这些忽略值时我们默认给最后一台。而rand的这种缺陷,恰好使得767(我取了 1000的精度)以后的值概率较低,互相弥补了下。
      在UDP的这一块,根据CPU的个数产生对应的线程来绑定不同的端口.反正上文的方法是无锁的,跑得肯定畅快。而这些端口的信息当然是交给更新服务器给客户端,客户端也是根据一个概率来选择连接。


邮局路由流程图


该程序里虽然用到了2个协议UDP和TCP,但是执行的动作都很简单。TCP负责在指定时间内更新自己服务器信息,UDP负责反馈这些信息给用户。在采用UDP上,我从这几个方面考虑:

  • 需求上,客户单只需要获得一个要连接的信息包即可,那么发起的动作只是简单的请求-接受这么个回合。即使UDP包出错,那么在1秒内完成这样的回合可以是十个左右(最佳情况),即时不是,那延迟个2-3秒,从登陆这个需求来说也是完全可以的。
  • 效率上,只是这一个简单的回合,建立起一个TCP花费的效率都比其高,更重要的是为这样的小回合再进行一个机器部署不合算,并且也很容易在打规模登陆的时候宕掉。UDP,无限的并发可能。即时处理不过来,也仍然屹立不倒。

邮局路由详细图

 

 

 

 

实例代码目录说明

以上的代码目录是邮局路由图,也大致体现了上文所说的框架大体样貌。因为整个流转的流程是这样的:
先来简单说下各个目录里的文件:
源文件/
PostofficeRoute.cpp:是个main程序,启动各种线程和网络库。其中UDP是根据CPU的数量来自动创建线程,能达到最高效使用。启动完毕后,就没main的什么事了,要做的只是等待各个线程的返回。
CRoutePublic.cpp:是一些main里公用的函数,比如拦截一些退出键,取得系统信息等
CConfigManager.cpp:配置文件
Standalone/
CRouteRand.cpp:一个随机的比例抽取对象,内容详见上一篇

下面是最主要的三个目录
Bridge/ 抽像NetWork和Logic之间的接口,因为邮局路由比较简单,所以没做队列的中间转换。
CUserLogicBridge/ CUserNetBridge:用户的网络接口和逻辑接口。网络接口包含了一些比如端口信息,sockaddr_in的结构信息等等,而逻辑接口里包含了对象的内存地址等等信息。这两个接口主要是为了一些信息的冗余和预留。

Logic/
CWorldRouteClient.cpp:网络数据提交至此的一个逻辑处理。该类里主要是一个成员函数的数组,根据协议的编号来实行自动跳转执行

NetWord/
CPublicSocket.cpp: 目前只包括一些协议正确与否的检测
CRouteClientUDP.cpp:包含一个UDP的网络处理程序和若干个跳转逻辑
CRouteServerTCP.cpp:同上

接下来模拟下数据流,当收到一个客户端UDP的请求后:
UDP的线程之一收到请求后,进行CPublicSocket的检测,检测通过,取到该连接的逻辑接口和网络接口,加上协议然后交给CWorldRouteClient.cpp的跳转函数到具体的实现函数里执行。

 

算法的示例代码

      在UDP的这一块,根据CPU的个数产生对应的线程来绑定不同的端口.反正上文的方法是无锁的,跑得肯定畅快。而这些端口的信息当然是交给更新服务器给客户端,客户端也是根据一个概率来选择连接。

 

/*
    VS2008下编译通过

    如有BUG和错误,请给在下一个消息,感激不尽
    QQ: 2#4#2#1#0#6#7#6#4    #表示为空
    Mail: lin_style#foxmail.com    #替换成@ 
*/

// 0xtiger_Rand.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

//
//测试的次数
const int TEST_COUNT = 40000;
//
//服务器台数
const int SERVER_NUMBER_OF = 5;
//
//服务器单台最大人数
const int SERVER_PEOPLE_SIZE_MAX = 25000;
//
//
const int PEOPLE_PRICISION_PROPORTION = 1000;

//
//总人数
int ServerSumPeople=0;
//
//服务器信息结构体
//int ServerInfo[SERVER_NUMBER_OF];
int ServerInfo[SERVER_NUMBER_OF];
//
//服务器人数的比例存放
int ServerProportion[SERVER_NUMBER_OF];
//
//服务器人数计算比例概率的偏移值
int ServerProportionOffset[SERVER_NUMBER_OF];
//
//记录被选了多少次
int ServerSelectRecord[SERVER_NUMBER_OF];

void Tracer_ServerSelectRecord()
{
	cout<<"server select record"<<endl;

	for(int i=0; i<SERVER_NUMBER_OF; ++i)
	{
		cout<<"num "<<i<<":"<<ServerSelectRecord[i]<<endl;
	}

	cout<<"*************end*************"<<endl<<endl;
}

void Tracer_ServerInfo()
{
	cout<<"server pepole"<<endl;

	cout<<"now sum people:"<<ServerSumPeople<<endl;

	cout<<"max people size of a server:"<<SERVER_PEOPLE_SIZE_MAX<<endl;

	for(int i=0; i<SERVER_NUMBER_OF; ++i)
	{
		cout<<"num "<<i<<":"<<ServerInfo[i]<<endl;
	}

	cout<<"*************end*************"<<endl<<endl;
}

void Tracer_ServerProportion()
{
	int i;
	cout<<"server Proportion"<<endl;

	cout<<"max people_pricision_proportion:"<<PEOPLE_PRICISION_PROPORTION<<endl;

	for(i=0; i<SERVER_NUMBER_OF; ++i)
	{
		cout<<"num "<<i<<":"<<ServerProportion[i]<<endl;
	}

	cout<<"max people_pricision_proportion offset:"<<endl;
	for(i=0; i<SERVER_NUMBER_OF; ++i)
	{
		cout<<"num "<<i<<":"<<ServerProportionOffset[i]<<endl;
	}

	cout<<"*************end*************"<<endl<<endl;	
}

void InitServerInfo()
{
	ServerInfo[0] = 1;
	ServerInfo[1] = 2;
	ServerInfo[2] = 500;
	ServerInfo[3] = 0;
	ServerInfo[4] = 0;

	int i;
	for(i=0; i<SERVER_NUMBER_OF; ++i)
	{
		ServerSumPeople+=ServerInfo[i];
	}
}

//
//计算比例 
void CtrlProportion()
{
	int i;
	int nSumServerProportion=0;
	double d;
	
	for(i=0; i<SERVER_NUMBER_OF; ++i)
	{ 		
		//
		//判断是否超出单台上限
		if( SERVER_PEOPLE_SIZE_MAX<ServerInfo[i] )
		{
			ServerProportion[i] = 0;
		}
		else
		{				
			ServerProportion[i] = (SERVER_PEOPLE_SIZE_MAX-ServerInfo[i]) / (double)SERVER_PEOPLE_SIZE_MAX * PEOPLE_PRICISION_PROPORTION;		
		}

		nSumServerProportion += ServerProportion[i];
	}

	for(i=0; i<SERVER_NUMBER_OF; ++i)
	{		
		ServerProportion[i] = ServerProportion[i] / (double)nSumServerProportion * PEOPLE_PRICISION_PROPORTION;
	}

	ServerProportionOffset[0]=ServerProportion[0];
	for(i=1; i<SERVER_NUMBER_OF; ++i)
	{
		ServerProportionOffset[i] = ServerProportionOffset[i-1]+ServerProportion[i];
	}

}
 
int GetRandObject(int nRandBase)
{
	for(int i=0; i<SERVER_NUMBER_OF; ++i)
	{
		int nBegin = ServerProportionOffset[i] - ServerProportion[i];
		int nEnd = ServerProportionOffset[i];
		if( nRandBase>=nBegin&& nRandBase<nEnd )
		{
			return i;
		}
	}

	return SERVER_NUMBER_OF-1;
}

int _tmain(int argc, _TCHAR* argv[])
{
	srand( (unsigned)time( NULL ) );
	InitServerInfo();
	CtrlProportion();

	Tracer_ServerInfo();
	Tracer_ServerProportion();

	int i;
	for(i=0; i<TEST_COUNT; ++i)
	{
		int nRecord;
		//
		//rand 0-32767
		nRecord = GetRandObject( rand()%PEOPLE_PRICISION_PROPORTION );
		ServerSelectRecord[nRecord]++;
		ServerInfo[nRecord]++;
		ServerSumPeople++;

		//CtrlProportion();    //是否每次都进行比例纠正
	}


	Tracer_ServerSelectRecord();

	cout<<"now server people:"<<endl;
	Tracer_ServerInfo();

	return 0;
}

/*
out put:
测试一:
在注释掉这句情况下//是否每次都进行比例纠正

测试次数为10000次,每台上限为2500次。(因为算法以上限数进行比例计算,超出则失衡)
初始人数
	ServerInfo[0] = 1;
	ServerInfo[1] = 2;
	ServerInfo[2] = 500;
	ServerInfo[3] = 0;
	ServerInfo[4] = 0;
得出结果
now server people:
server pepole
now sum people:10503
max people size of a server:2500
num 0:2086
num 1:2151
num 2:2132
num 3:2116
num 4:2018
虽然最高值偏差到140人,差不多是5%-7%偏差(我没计算错吧),具体还跟rand()这个值有关。不过我已经非常满意
这样的分布了。

测试二:
开启注释的//是否每次都进行比例纠正
测试次数改为20000,进行超出测试
now server people:
server pepole
now sum people:20503
max people size of a server:2500
num 0:2498
num 1:2498
num 2:2498
num 3:2498
num 4:10511
发现千分之一的人数误差。因为超出的默认都在最后一台所以人数偏大
*/
 

 

 

 

 

 

 

1
0
分享到:
评论

相关推荐

    EMAIL服务器源代码.zip

    电子邮件服务器是互联网上的一种核心服务,它允许用户发送和接收电子邮件。在深入讲解"EMAIL服务器源代码.zip"之前,我们先来理解电子邮件的工作原理和服务器的角色。 电子邮件系统基于SMTP(简单邮件传输协议)...

    简单的SMTP,pop3服务器

    SMTP服务器会处理路由和投递。 4. **POP3接收**:用户通过POP3协议连接到服务器,获取新邮件。服务器可能需要检查是否有新邮件,并提供下载列表。 5. **群发邮件**:实现群发功能,服务器需要处理多个收件人的邮件头...

    windows Server下架设邮件服务器

    邮件服务器遵循SMTP(简单邮件传输协议)用于发送邮件,POP3(邮局协议)或IMAP(因特网消息访问协议)用于接收邮件。SMTP处理邮件的发送,而POP3或IMAP则负责用户从服务器上下载邮件。 在Windows Server 2003中,...

    纯Java的一个邮件服务器

    5. **邮件过滤和路由**:James支持自定义规则,可以根据发件人、收件人或其他条件对邮件进行过滤和路由,提供反垃圾邮件和自动转发等功能。 6. **Web管理界面**:James带有Web管理界面,用户可以通过浏览器配置...

    pmta.gz邮箱服务器资源

    标题中的“pmta.gz邮箱服务器资源”指的是Postfix Message Transfer Agent(PMTA)的压缩文件,这是一款强大的电子邮件服务器软件,常用于批量发送邮件。PMTA以其高效、可扩展性和对发送策略的精细控制而受到许多...

    计算机软件-编程源码-最新mail服务器.zip

    8. **邮件路由**:邮件服务器需要知道如何将邮件转发到正确的目的地,可能包含DNS查询和MX记录解析。 9. **API接口**:可能提供RESTful API,允许其他应用或服务与邮件服务器交互。 10. **配置文件**:服务器的...

    搭建企业邮局系统全攻略 (CHM).rar

    【标题】"搭建企业邮局系统全攻略 (CHM)" 是一本专注于介绍如何构建企业级邮件服务器的教程,主要目标是帮助企业建立稳定、安全且高效的企业邮局解决方案。该资源可能包含了一系列步骤、最佳实践以及可能遇到的问题...

    james-2.3.2邮件服务器

    6. **邮件路由**:James具备邮件路由功能,可以根据邮件目的地自动转发邮件到正确的邮件服务器。 7. **反垃圾邮件**:集成了一些基本的反垃圾邮件策略,例如RBL(Real-time Blackhole List)查询,可阻止已知的垃圾...

    基于java的邮件服务器源程序.zip

    Java邮件服务器源程序是用于处理电子邮件传输的应用程序,它基于Java编程语言实现。在Java中,我们可以使用JavaMail API来创建、发送、接收和管理邮件。这个压缩包可能包含了一个完整的邮件服务器实现,如James或者...

    邮件服务器基本知识总结

    首先,邮件服务器是通过SMTP(简单邮件传输协议)和POP3(邮局协议)或IMAP(Internet消息访问协议)来实现邮件的发送和接收。SMTP用于将邮件从一个服务器传输到另一个服务器,而POP3和IMAP则允许用户从邮件服务器...

    邮件服务器安装实验报告..docx

    最后的验证环节确保了DNS与邮件服务器的正确连接,使得邮件可以被正确路由到相应的邮件接收者。 5. SMTP与POP3协议理解: SMTP是邮件传输的核心协议,它定义了如何将邮件从一个服务器发送到另一个服务器。POP3则...

    邮件服务器和MX之间的设置关系

    这些记录指示了邮件应该被路由到哪个服务器进行处理。如果MX记录存在,邮件服务器就会将邮件发送到该记录指定的IP地址。如果MX记录不存在或配置不正确,邮件可能无法送达,导致通信中断。 在设置MX记录时,需要遵循...

    ExtMail邮件服务器.zip

    SMTP服务器需要正确配置DNS以确保邮件能正确路由到目的地。同时,SMTP服务器还支持身份验证,防止未经授权的用户滥用服务。 3. **POP3/IMAP4服务器**:用户可以通过POP3或IMAP4协议从邮件服务器下载邮件。ExtMail...

    项目7配置与管理电子邮件服务器.zip

    3. **域名和DNS设置**:配置MX(邮件交换)记录是确保邮件能正确路由到邮件服务器的关键。同时,需要理解SPF(发信策略框架)、DKIM(域名密钥识别邮件)和DMARC(域消息认证、报告与一致性)等反垃圾邮件策略,以...

    基于Linux架构邮件服务器.doc

    Linux邮件服务器能够提供安全、可靠的服务,支持多种邮件协议,如SMTP(简单邮件传输协议)、POP(邮局协议)和IMAP(因特网消息访问协议),以满足不同用户的需求。 1.2 架构邮件服务器的任务目的 构建基于Linux...

    Centos6.3下搭建sendmaildovecot邮局服务器.doc

    在设置邮件服务器之前,必须确保您的域名已经正确设置了 MX 记录,以便外部邮件能正确路由到您的服务器。 2. **安装 dovecot**: Dovecot 是一个开源的 IMAP 和 POP3 邮件服务器,适用于 Linux 和其他类 Unix 系统...

    基于Java的邮件服务器源程序.7z

    Java的邮件服务器源码提供了实现SMTP(简单邮件传输协议)、POP3(邮局协议)和IMAP(因特网消息访问协议)等标准协议的类库。这些协议是电子邮件系统的核心,使得用户能够发送、接收和管理邮件。 1. SMTP(Simple ...

    MD邮件服务器-MDaemonServer

    MD邮件服务器-MDaemonServer是一款备受推崇的企业级电子邮件服务器软件,专为满足小型到大型组织的邮件通信需求而设计。这款软件以其稳定、高效和安全的特性,在市场上树立了良好的口碑,被誉为“最好的邮件服务器...

Global site tag (gtag.js) - Google Analytics