网络游戏中网络模块浅析
在网络游戏中,不论是服务端还是客户端都需要网络通讯的功能模块,而一个优秀的成熟的网络通讯模块,又可以用于多个游戏产品中。
于是,在学习的过程中,设计和实现一个可复用的网络通讯模块,变得非常的有意义。
通过思考我们可以一步一步的来实现这样的模块:
我们应该知道对原生的Socket API进行封装是有必要的,也许当前服务端是运行在window环境下,也许改天就有可能要在Linux下面运行。而对原生Socket API进行封装,可以很方便的让我们进行两者的切换,并且不需要我们付出多大的代价。
可以像下面这样封装,放在SOCKET_API命名空间下面,其他API类似:
UINT SOCKET_API::recv_ex ( SOCKET s , void * buf , UINT len , UINT flags )
{
#if defined __LINUX__
….
#elif defined __WINDOWS__
….
#endif
}
我们都知道游戏通常都是使用TCP来实现通讯的,而TCP需要下面这些最基本的功能及对SOCKET进行设置或者获取信息的函数。
server |
client |
bind listen accept send recv close |
connect send recv close |
我们希望把这些功能都封闭到一个CSocket类中。
class CSocket
{
public :
构造函数,析构函数,初始化函数……
BOOL connect (const CHAR* host, UINT port) ;
UINT send (const VOID* buf, UINT len, UINT flags = 0) ;
SOCKET accept( struct sockaddr* addr, UINT* addrlen ) ;
其他函数,例如receive ,bind,listen,close,get和set变量接口,有效性判断等等……
public :
SOCKET m_SocketID;
SOCKADDR_IN m_SockAddr;
CHAR m_Host[IP_SIZE];
UINT m_Port;
}
不论是服务端还是客户端都没有办法做到一收到消息就马上响应请求,然后直接把
这个消息丢弃掉。所以我们需要有一个地方可以保存这些消息,这时我们可以定义输入和输出消息缓冲区的类,当我们发送或者接收消息时,先把消息放到相应的消息缓冲区里,然后再进行相应的处理。而这个缓冲区最好是环形的,当缓冲区不够用的时候能够自己增加缓冲区大小,当然需要有个上限。
当我们有了消息缓冲区后,这时我们需要通过一种方式,把消息放入缓冲区和从缓
冲区中把消息读取出来,在放入和取出的时候可能还需要伴随着加密与解密操作。由于在游戏中,一样连接通常都代表着一个玩家。
我们可以定义个CPlayer类:
class CPlayer
{
public:
ProcessInput(); //读取网络上的消息并放在输入消息缓冲区
ProcessOutput(); //把输出消息缓冲区中的消息发送出去
ProcessMsg(); //处理收到的消息,把相应的消息交给相应的处理函数
private:
输入缓冲区
输出缓冲区
}
到这里时,上面的内容通常都可以作为服务端和客户端通用的代码。
下面我们重点了解一下,服务端是怎么样接着处理收到的消息的,在我接触过的服务端中,有两种不同的框架,不过处理方式大同小异,顺便提下。
第一种,就像我在游戏服务端逻辑模块处理框架中说的差不多,在种情况之下,服务端的逻辑模块会划分成很多的DLL模块,比如:
战斗系统就是一个BattleSys.dll
技能系统就是一个MagicMgr.dll
角色系统就是一个RoleMgr.dll
然后我们有一个消息中心,当网络模块收到消息之后把消息发送给消息中心,消息中心再把消息Buffer发送给感兴趣的DLL模块,然后这些DLL模块用消息Buffer生成相应的CMsg(每个消息都有消息头,根据消息头的消息编号可以识别相应的CMsg),并执行CMsg的ProcessMsg消息处理函数。
第二种,没有把逻辑模块划分成很多个DLL,游戏逻辑都在Server中进行处理。收到网络消息之后,直接通过管理器类CpacketFactoryMgr创建出CMsg类(查找相应的消息创建工厂,并用它创建出CMsg类),并执行CMsg的ProcessMsg消息处理函数。
其实就是这样的过程,注册XXX到管理器 => 通过XXX的ID到管理器中找到XXX => XXX.DoSomething()
先说到这里了…继续学习之后再继续……