贸易时代的文档(一)--登陆服务器

登陆服务器

 

需求分析

功能概述:

       在大型网络游戏里,客户端都不是直接连接到处理游戏消息的服务,而是先连接到登陆服务器,然后登陆服务器再根据游戏消息的不同,分别转发给逻辑服务器或者地图服务器。登陆服务器在这里起到是一个网关的功能,把来自不同地方的玩家游戏消息转发给内部进行逻辑处理的服务器,同时也把来自内部服务器的游戏消息发送给不同地方的玩家。

 

功能分析

1.  客户端的网络连接

2.  验证新连接客户端的身份

3.  将客户端发送过来的数据转发给地图服务器或者逻辑服务器

4.  把逻辑服务器或者地图服务器的游戏消息发送给客户端

 

客户端网络连接:

       在登陆服务器里,相应客户端的连接是一个最重要的功能,他必须要能够受理大量的客户端的连接,因为是游戏的关系,需要具备相应安全的网络连接,因此采用TCP连接,这样大量的连接,采用那种网络I/O类型就会关系到服务器的承受能力,如果采用常用的阻塞+线程的方式处理客户端连接是非常不现实的,它将浪费服务器大量的时间在于线程切换上。而且在windows操作系统下线程开到2000个已经是极限了,而游戏服务器动则上万人在线,这样肯定是不行的。因此,通过查阅资料,在windows操作系统下,解决这类大量连接的方案通常采用完成端口I/O模型。

 

完成端口I/O模型

       完成端口是微软为了响应大规模的网络应用而推出的一项技术,其核心是在操作系统内部建立一个通知队列,当应用程序发出一个重叠I/O请求后,由操作系统对这个I/O请求进行处理,处理完毕后放入一个通知队列中,应用程序再从这个消息队列里取出本次I/O的返回结果,并根据返回结果进行自己的处理。

 

完成端口的一般处理流程:

1:创建一个完成端口。

2:创建一个线程A

3A线程循环调用GetQueuedCompletionStatus()函数来得到IO操作结果

4:主线程循环里调用accept等待客户端连接上来。

5:主线程里accept返回新连接建立以后,把这个新的套接字句柄用CreateIoCompletionPort关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用,因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。

6:主线程继续下一次循环,阻塞在accept这里等待客户端连接。

7WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。

8A线程里的GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。

9:在A线程里对这些数据进行处理(如果处理过程很耗时,需要新开线程处理),然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。

完成端口的封装

因为完成端口应用是一系列API的操作,在应用程序里直接使用,并不是太方便,所以对完成端口进行了简单封装。

 

class CIOCP

{

public:

     virtual void OnRead(void * p, char *buf, int len){};

     virtual void OnAccept(SOCKET socket);

     virtual void OnClose(void * p){};

     bool SetIoCompletionPort(SOCKET socket, void *p);

     bool Init(void);

     bool Listen(char * ip, int port);

};

 

我们对完成端口模型抽象出3个虚函数,OnReadOnAcceptOnClose,在实际应用的时候根据需要可以以重载的方式重载这三个函数,并且在这些重载过的函数里实现需要的功能。

SetIoCompletionPort()是留出来的一个接口函数,它的作用是把一个socket与一个程序自定义结构体相关联,当某个socket发生OnRead读取,OnClose关闭事件的时候,会把与socket关联的这个结构体变量再传回来,至于这个结构体变量就完全由用户自己设计了,这里传入和传出的都是该结构体变量的指针。

       Init()是初始化网络用,Listen()是作为服务器的时候开始进行监听。在监听设置完成后就退出,并不阻塞在里面。目前完成端口暂时不作为客户端使用。

 

完成端口类的实现方案

       通过完成端口类的学习,我们知道了要进行完成端口类的操作至少需要2个线程,一个线程用于接受新客户端的连接,另外一个线程阻塞读取完成端口的通知队列(这部分线程可以根据需要进行增加)。因此我们设计的完成端口类里把这两个线程一起封装到类里,一个是AcceptThread线程,采用阻塞accept的方式获得玩家的连接请求,然后触发OnAccept事件。另外一个是WorkThread线程,采用阻塞读取通知队列的方式进行处理,并根据返回的信息触发相应的OnReadOnClose事件。

 

 

详细设计

 

登陆服务器的总体设计

       在拥有的完成端口类以后,我们开始设计逻辑服务器的功能。逻辑服务器主要是接受客户端的连接,并且把客户端发过来的游戏消息转发给对应的服务器。在接受客户端的连接的同时,还需要对客户端进行身份验证,验证通过后才能让客户端开始进行游戏。

       因为登陆服务器使用完成端口,因此在接受客户端连接模块,是继承自我们设计的完成端口类(CIOCP),并从中重载了OnAcceptOnReadOnClose三个函数。这三个函数分别对应着客户端连接,客户端发送数据包,客户端关闭。

       同时服务器还负担有其它服务器的连接,因此,在处理其它服务器的网络处理模块也是继承自CIOCP。只不过它于响应客户端的模块不同,它没有重载OnClose这个函数,因为服务器是不会主动进行关闭的(因为服务器异常退出除外)

 

客户端的连接信息:

       在客户端连接到服务器以后,服务器需要对客户端身份进行验证,在验证通过后,进入游戏运行后,还需要对玩家的一些数据进行操作,这样我们就需要定义结构体变量用户保存玩家的基本信息,这些信息包括:编号(玩家在服务器端里的唯一标识符),状态,socket等。除了玩家的基本信息外,针对玩家在服务器端里可能的操作,我们把这些信息封装进入了一个LoingSession类,并增加了消息缓冲区,以及一些针对玩家的网络操作send等。

       因为登陆服务器需要响应大量客户端的连接,封装有客户端连接信息类的实体并不适合随用随生成(new)的方式使用,因此,我们在服务器端的设计的时候,把这些客户端连接信息声明为全局对象数组session[],并规定了数组的大小MAX_SESSION,大小暂时定为200。同时把玩家的编号当做对象数组下标使用,这样可以省去通过编号查找玩家对应的数组的过程。

 

       下图是客户端的连接流程图。

 

收到某个客户端发送的游戏消息处理流程图

 

消息处理函数流程图:

 

登陆验证流程图:

 

客户端socket关闭事件处理流程图:

 

       对于其它服务器连接到登陆服务器就比较简单了,因为其它服务器都和登陆服务器出于同一个作业环境下,不会存在非法登陆的情况。所以,其它服务器的连接流程比较简单,只需要报告自己是那个服务器即可。而登陆服务器在相应其它服务器发出的游戏消息也和响应客户端的游戏消息类似。

其它服务器连接流程图:

 

收到其它服务器发出的数据处理流程图:

服务器端消息处理过程

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值