请选择 进入手机版 | 继续访问电脑版

NoahFrame

 找回密码
 Register Now
搜索
热搜: redis mysql tutorial
查看: 3071|回复: 0

第十章 NF分布式服务器解决方案— 网络通信详解

[复制链接]

30

主题

111

帖子

652

积分

Administrator

Rank: 9Rank: 9Rank: 9

积分
652
发表于 2017-4-17 18:05:56 | 显示全部楼层 |阅读模式
NF(https://github.com/ketoo/NoahGameFrame)全称为 NoahGameFrame。

NF最早为客户端设计,后来随着时代的变化,而为自己又转为服务器开发,故在吸收了众多引擎的优点后(包含Ogre的插件模式&模块化管理机制,Bigworld的数据管理&配置机制,类似MYGUI的接口层次设计),经过多年演化和实践,变成了一套游戏开发J解决方案。方案中包含开源的服务器架构,网络库(站在libevent的肩膀上),和unity3d的demo源码。现在NF已经在多个公司的多个项目中使用,其中包含知名产品 《全民无双》。

关键词

NoahGameFrame/NoahFrame/NFrame/NF
集群/负载均衡/分布式
网关服务器 GateServer 心跳 多线程/线程池 开源网络框架/模型
一致性hash算法/ConsistentHash
游戏开发中的设计模式/数据结构
Socket Nagle/粘包/开源游戏服务器/ Game Server
插件/模块/插件加载/热更新



讲解了不少NF的设计概念,NF作为服务器框架,其中重中之重的模块当属网络通信模块了,此模块设计的好坏,是否与主框架耦合过高,直接体现了设计者的设计能力以及框架质量(绝大多数人写的服务器代码,网络库都作为主体部分和主业务深度耦合)。好在nf的插件式架构,NetPlugin也是严格遵守NF插件架构的面相接口编程思想,因此无需担忧NetPlugin和主框架耦合问题,NetPlugin也将提供各种接口供外界使用,非常简单即可熟悉使用。

网络服务器中,互相通信依赖通信协议,否则谁也不认识谁,当前通信协议也好几种,常见的是udp, tcp, http 以及websocket的frame。服务器中常用的是tcp以及http协议,客户端链接服务器使用的协议常用有tcp, http 以及websocket(一些特殊情况也会考虑udp协议)。在这些协议中,又涉及大小端,粘包等技术细节需要处理,最重要的还有高负载,一个处理不好,就会使得服务器运行不稳定,这里就从头开始讲解一下与网络通信相关的知识。

NF使用的是libevent作为基础网络库,上层封装自己的NetObject 和NetModule,把NFNetPlugin作为承载网络部分的插件。
其中重要的类有NFCNet, NFCNetModule 和 NFCNetClientModule。 这里就详细讲解为什么会这样分以及他们的使用方法。

我自诩为程序设计师,非码农,因此我在设计一样事物的时候,总是会优先考虑抽象出通用的接口以方便后期替换,或者满足最少知识原则,里斯替换原则等, 最后就是集成为单纯的module供外接使用,而外界并不需要了解module内部的信息,以达到信息封装的目的。

那为什么会有NFCNet, NFCNetModule 和 NFCNetClientModule 这样3个类呢?
NFCNet封装了基础的net操作(libevent), NFCNetModule在NFCNet的基础上封装了作为服务器使用时的module,NFCNetClientModule则在基于NFCNetModule的基础上,提供了可以连接多种类型服务器的module。

首先有NFCNet类提供基础功能,包含
1:初始化客户端以及服务器函数接口
2:帧执行接口
3:注册函数callback
4:发送消息等接口,需要各种接口方便后续业务编程
5:网络对象的管理接口( 这时候是不是需要设计NetObject了呢?)
接口粗略设计为:
  1. class NFINet
  2. {
  3. public:
  4.     //need to call this function every frame to drive network library
  5.     virtual bool Execute() = 0;

  6.     virtual void Initialization(const char* strIP, const unsigned short nPort) = 0;
  7.     virtual int Initialization(const unsigned int nMaxClient, const unsigned short nPort, const int nCpuCount = 4) = 0;

  8.     //send a message with out msg-head[auto add msg-head in this function]
  9.     virtual bool SendMsgWithOutHead(const int16_t nMsgID, const char* msg, const uint32_t nLen, const int nSockIndex = 0) = 0;
  10.     //send a message to all client[need to add msg-head for this message by youself]
  11.     virtual bool SendMsgToAllClient(const char* msg, const uint32_t nLen) = 0;
  12.     //send a message with out msg-head to all client[auto add msg-head in this function]
  13.     virtual bool SendMsgToAllClientWithOutHead(const int16_t nMsgID, const char* msg, const uint32_t nLen) = 0;

  14.     virtual bool CloseNetObject(const int nSockIndex) = 0;
  15.     virtual NetObject* GetNetObject(const int nSockIndex) = 0;
  16.     virtual bool AddNetObject(const int nSockIndex, NetObject* pObject) = 0;

  17.     virtual bool IsServer() = 0;
  18. };
复制代码

里面包含了客户端服务器网络模块初始化、发送消息给某个具体的客户端、广播、连接事件和接收消息的处理回调等接口,适合于大部分C/S结构的通讯,其中提供通用抽象的NetObject和MsgHead意味着更容易维护以及修改扩展。

NF中何为NetObject网络对象呢,主要是NF为了屏蔽不同网络库之间的差异抽象出的一套系统,它类似linux中的文件描述符fd。任何一个连接连到服务器的时候,服务器NFCNet就会为此产生一个NetObject来代替它,通过NetObject可以与不同网络库之间建立联系,实现写缓存copy,读取缓存copy等功能。我们来看看NetObject的设计:

  1. class NetObject
  2. {
  3. public:

  4.     int AddBuff(const char* str, uint32_t nLen)
  5.     int CopyBuffTo(char* str, uint32_t nStart, uint32_t nLen)
  6.     int RemoveBuff(uint32_t nStart, uint32_t nLen)

  7.     const char* GetBuff()
  8.     int GetBuffLen() const


  9.     const std::string& GetSecurityKey() const
  10.     void SetSecurityKey(const std::string& strKey)

  11.     int GetConnectKeyState() const
  12.     void SetConnectKeyState(const int nState)

  13.     bool NeedRemove()
  14.     void SetNeedRemove(bool b)

  15.     const std::string& GetAccount() const
  16.     void SetAccount(const std::string& strData)

  17.     int GetGameID() const
  18.     void SetGameID(const int nData)

  19.     const NFGUID& GetUserID()
  20.     void SetUserID(const NFGUID& nUserID)

  21.     const NFGUID& GetClientID()
  22.     void SetClientID(const NFGUID& xClientID)

  23.     int GetRealFD()
  24. }
复制代码

我们可以看到,NetObject的主要功能的对其进行缓存读写并维持连接状态和用户状态数据每次NFCNet执行execute函数的时候就会去调用event_base_loop执行网路库loop,进而调用注册的回调函数conn_readcb把所有的buff数据读取到NetObject,再调用dismantle从NetObject的buff提取数据进行解析成合法的消息包,然后调用用户注册的callback接口,继由业务函数会处理此消息,代码如下:
  1. void NFCNet::conn_readcb(struct bufferevent* bev, void* user_data)
  2. {
  3.     NetObject* pObject = (NetObject*)user_data;
  4.     if (!pObject)
  5.     {
  6.         return;
  7.     }

  8.     NFCNet* pNet = (NFCNet*)pObject->GetNet();
  9.     if (!pNet)
  10.     {
  11.         return;
  12.     }

  13.     if (pObject->NeedRemove())
  14.     {
  15.         return;
  16.     }

  17.     struct evbuffer* input = bufferevent_get_input(bev);
  18.     if (!input)
  19.     {
  20.         return;
  21.     }

  22.     size_t len = evbuffer_get_length(input);

  23.     //////////////////////////////////////////////////////////////////////////

  24.     char* strMsg = new char[len];
  25.     if (evbuffer_remove(input, strMsg, len) > 0)
  26.     {
  27.         pObject->AddBuff(strMsg, len);
  28.     }

  29.     delete[] strMsg;

  30.     while (1)
  31.     {
  32.         if (!pNet->Dismantle(pObject))
  33.         {
  34.             break;
  35.         }
  36.     }
  37. }

  38. bool NFCNet::Dismantle(NetObject* pObject)
  39. {
  40.     bool bNeedDismantle = false;

  41.     int len = pObject->GetBuffLen();
  42.     if (len > NFIMsgHead::NF_Head::NF_HEAD_LENGTH)
  43.     {
  44.         NFCMsgHead xHead;
  45.         int nMsgBodyLength = DeCode(pObject->GetBuff(), len, xHead);
  46.         if (nMsgBodyLength > 0 && xHead.GetMsgID() > 0)
  47.         {
  48.             int nRet = 0;
  49.             if (mRecvCB)
  50.             {
  51.                 mRecvCB(pObject->GetRealFD(), xHead.GetMsgID(), pObject->GetBuff() + NFIMsgHead::NF_Head::NF_HEAD_LENGTH, nMsgBodyLength);

  52.                 mnReceiveMsgTotal++;
  53.             }

  54.             pObject->RemoveBuff(0, nMsgBodyLength + NFIMsgHead::NF_Head::NF_HEAD_LENGTH);

  55.             bNeedDismantle = true;
  56.         }
  57.         else if (0 == nMsgBodyLength)
  58.         {
  59.             bNeedDismantle = false;
  60.         }
  61.         else
  62.         {
  63.             bNeedDismantle = false;
  64.         }
  65.     }

  66.     return bNeedDismantle;
  67. }
复制代码


我们可以看到,其中用到了NFIMsgHead。NFIMsgHead是NF中消息包的包头定义,目前包头长度固定为NFIMsgHead::NF_Head::NF_HEAD_LENGTH大小(6), 分别为消息头ID:Message Head(2) 和消息体大小:MsgSize(4) 。NFIMsgHead的设计,又让NF的网络模块很容易嵌入到旧的游戏系统中去,甚至轻松实现与旧系统的通信。

NFCNet里因为良好的设计思路和对接口的深入理解,可以非常快的实现自己的网络模块,下面我们分别来看如何使用module方式和非module方式来快速实现TCP客户端和服务器。


不使用module方式:
1. TCP-server (TestServer.cpp)

  1. class TestServerClass
  2. {
  3. public:
  4.     TestServerClass()
  5.     {
  6.         pNet = new NFCNet(this, &TestServerClass::ReciveHandler, &TestServerClass::EventHandler);
  7.         pNet->Initialization(1000, 8088);
  8.     }

  9.     void ReciveHandler(const int nSockIndex, const int nMsgID, const char* msg, const uint32_t nLen)
  10.     {
  11.         std::string str;
  12.         str.assign(msg, nLen);

  13.         pNet->SendMsgWithOutHead(nMsgID, msg, nLen, nSockIndex);
  14.         std::cout << " fd: " << nSockIndex << " msg_id: " << nMsgID << " thread_id: " << GetCurrentThreadId() << std::endl;
  15.     }

  16.     void EventHandler(const int nSockIndex, const NF_NET_EVENT e, NFINet* p)
  17.     {
  18.         std::cout << " fd: " << nSockIndex << " event_id: " << e << " thread_id: " << std::this_thread::get_id() << std::endl;
  19.     }

  20.     void Execute()
  21.     {
  22.         pNet->Execute();
  23.     }

  24. protected:
  25.     NFINet* pNet;
  26. };

  27. int main(int argc, char** argv)
  28. {
  29.     TestServerClass x;

  30.     while (1)
  31.     {
  32.         x.Execute();
  33.     }

  34.     return 0;
  35. }
复制代码

仅需要两个参数(最大连接数,监听端口)和两个回调函数(EventHandler, ReciveHandler),我们就可以实现开启一个tcp服务器并且接收客户端的数据


2. TCP-client (TestClient.cpp,这里同时开启了1000个clients)
  1. class TestClientClass
  2. {
  3. public:
  4.     TestClientClass()
  5.     {
  6.         pNet = new NFCNet(this, &TestClientClass::ReciveHandler, &TestClientClass::EventHandler);
  7.         pNet->Initialization("127.0.0.1", 8088);
  8.         bConnected = false;
  9.     }

  10.     void ReciveHandler(const int nSockIndex, const int nMsgID, const char* msg, const uint32_t nLen)
  11.     {
  12.         std::string str;
  13.         str.assign(msg, nLen);

  14.         std::cout << " fd: " << nSockIndex << " msg_id: " << nMsgID /*<<  " data: " << str */ << " thread_id: " << std::this_thread::get_id() << std::endl;
  15.     };

  16.     void EventHandler(const int nSockIndex, const NF_NET_EVENT e, NFINet* p)
  17.     {
  18.         std::cout << " fd: " << nSockIndex << " event_id: " << e << " thread_id: " << std::this_thread::get_id() << std::endl;
  19.         if(e == NF_NET_EVENT_CONNECTED)
  20.         {
  21.             bConnected = true;
  22.         }
  23.     }

  24.     void Execute()
  25.     {
  26.         if(bConnected)
  27.         {
  28.             pNet->SendMsgWithOutHead(1, "1234567890", 10, 0);
  29.         }

  30.         pNet->Execute();
  31.     }

  32. protected:
  33.     NFINet* pNet;
  34.     bool bConnected;
  35. private:
  36. };

  37. int main(int argc, char** argv)
  38. {
  39.     std::list<TestClientClass*> list;

  40.     for (int i = 0; i < 1000; ++i)
  41.     {
  42.         TestClientClass* x = new TestClientClass();;
  43.         list.push_back(x);
  44.     }

  45.     while (1)
  46.     {
  47.         std::list<TestClientClass*>::iterator it = list.begin();
  48.         for (it; it != list.end(); ++it)
  49.         {
  50.             Sleep(1);

  51.             (*it)->Execute();
  52.         }
  53.     }

  54.     return 0;
  55. }
复制代码

TCP客户端只需要填入客户端IP和Port,以及两个事件回调函数(事件回掉:连接,断开连接;消息回掉:接受消息),即可发送数据到服务器进行通讯。


使用module方式:
使用module方式主要是指使用NFINeModule/NFCNetModule 和 NFINetClientModule/NFCNetClientModule 来建立复杂的服务器和客户端,主要是因为实际项目开发中,基于用户体验以及产品容灾的产品指标,同样要实现几个额外的功能:

1:动态扩容--即要求能动态增加连接到新的服务器
2:自动重连--断线后,要求会自动重连,以便恢复连接
3:负载均衡--当某些消息无特定处理服务器的时候,要求按照一致性原则发送到同类型的服务器

4:消息猎夺--能够自由的抢占式移除某些应用消息回调并增加新的消息回调

这2个module最重要的就是,实现了对于动态扩容,断线重连,以及负载方面的自动处理,而无需使用者关心这些需求的内部实现。为什么要设计2套netmodule呢?这是因为,NF的架构设计中很多服务器在运行的时候,既作为服务器接收其他Server/Client的请求,也有作为客户端而去连接上层服务器,各位看官在NF的源码中,看到的各种类似NFProxyServerNet_ClientPlugin/NFProxyServerNet_ServerPlugin就是属于此类。那么,相对来说,比 NFINet/NFCNet多了什么内容呢?

1:作为游戏应用服务器,主架构中设计了各种类型的服务器,比如 gameserver,worldserver等,具体在NFINetModule中有定义:
  1. enum NF_SERVER_TYPES
  2. {
  3.     NF_ST_NONE          = 0,    // NONE
  4.     NF_ST_REDIS         = 1,    //
  5.     NF_ST_MYSQL         = 2,    //
  6.     NF_ST_MASTER        = 3,    //
  7.     NF_ST_LOGIN         = 4,    //
  8.     NF_ST_PROXY         = 5,    //
  9.     NF_ST_GAME          = 6,    //
  10.     NF_ST_WORLD         = 7,    //
  11.     NF_ST_MAX                        = 8,    //

  12. };
复制代码


那么在连接的时候,NFNetClientModule 提供了相应的接口对各种服务器进行各种操作,包含各种事件回调、消息回调和发送消息等接口。
  1. NFINetClientModule
复制代码


2:链接的逻辑信息, 从1中我们得知,大量不同类型的服务器需要管理,比如多久发一次心跳维持链接,重发次数等,那么势必要设计一个类容纳这些数据,名字叫connectData:
  1. struct ConnectData
  2. {
  3.         ConnectData()
  4.         {
  5.                 nGameID = 0;
  6.                 nPort = 0;
  7.                 strName = "";
  8.                 strIP = "";
  9.                 eServerType = NF_ST_NONE;
  10.                 eState = ConnectDataState::DISCONNECT;
  11.                 mnLastActionTime = 0;
  12.         }

  13.         int nGameID;
  14.         NF_SERVER_TYPES eServerType;
  15.         std::string strIP;
  16.         int nPort;
  17.         std::string strName;
  18.         ConnectDataState eState;
  19.         NFINT64 mnLastActionTime;

  20.         NF_SHARE_PTR<NFINetModule> mxNetModule;
  21. };
复制代码


3:client实现负载均衡接口,主要对某一类型服务器发送消息的时候,消息可以均摊到所有的服务器
  1. virtual void SendBySuit(const NF_SERVER_TYPES eType, const std::string& strHashKey, const int nMsgID, const std::string& strData) = 0;
  2. virtual void SendBySuit(const NF_SERVER_TYPES eType, const std::string& strHashKey, const int nMsgID, const char* msg, const uint32_t nLen) = 0;
  3. virtual void SendBySuit(const NF_SERVER_TYPES eType, const int& nHashKey, const int nMsgID, const std::string& strData) = 0;
复制代码


具体的实现内容,限于篇幅,请直接观看NFINetModule/NFCNetModule和NFINetClientModule/NFCNetClientModule类,其中他们的实际使用,可以参考proxyserver的使用例子,比如NFCProxyServerToGameModule表示proxyserver连接到gameserver,连接源是从配置中(Server.xlsx)读取的同一区服的所有gameserver的id,ip,port等信息(这里没处理区服,默认全区全服),代码如下:
  1. NF_SHARE_PTR<NFIClass> xLogicClass = m_pClassModule->GetElement(NFrame::Server::ThisName());
  2.     if (xLogicClass)
  3.     {
  4.                 const std::vector<std::string>& strIdList = xLogicClass->GetIDList();
  5.                 for (int i = 0; i < strIdList.size(); ++i)
  6.                 {
  7.                         const std::string& strId = strIdList[i];

  8.             const int nServerType = m_pElementModule->GetPropertyInt(strId, NFrame::Server::Type());
  9.             const int nServerID = m_pElementModule->GetPropertyInt(strId, NFrame::Server::ServerID());
  10.             if (nServerType == NF_SERVER_TYPES::NF_ST_GAME)
  11.             {
  12.                 const int nPort = m_pElementModule->GetPropertyInt(strId, NFrame::Server::Port());
  13.                 const int nMaxConnect = m_pElementModule->GetPropertyInt(strId, NFrame::Server::MaxOnline());
  14.                 const int nCpus = m_pElementModule->GetPropertyInt(strId, NFrame::Server::CpuCount());
  15.                 const std::string& strName = m_pElementModule->GetPropertyString(strId, NFrame::Server::Name());
  16.                 const std::string& strIP = m_pElementModule->GetPropertyString(strId, NFrame::Server::IP());

  17.                 ConnectData xServerData;

  18.                 xServerData.nGameID = nServerID;
  19.                 xServerData.eServerType = (NF_SERVER_TYPES)nServerType;
  20.                 xServerData.strIP = strIP;
  21.                 xServerData.nPort = nPort;
  22.                 xServerData.strName = strName;

  23.                                 m_pNetClientModule->AddServer(xServerData);
  24.             }
  25.         }
  26.     }
复制代码



我们可以看见,设置好ConnectData数据后,使用m_pNetClientModule->AddServer(xServerData)添加进去即可,因此服务器动态扩容后,也是采用此方法进行连接,典型的比如连接world就是采用此方法。

番外知识:
在网络开发中,还有几个基础知识需要掌握,那怕是概念,不然交流起来可能会比较困难,其中主要有3方面:

1:粘包-拆包问题
因为TCP/IP协议是基于流式传输,因此它存在粘包问题那么什么是粘包问题呢?

TCP/IP协议基于字节流的传输服务,"流"意味着传输的数据是没有边界的。这不同于UDP提供基于消息的传输服务,其传输的数据是有边界的。TCP/IP的发送方无法保证对等方每次接收到的是一个完整的数据包。主机A向主机B发送两个数据包,主机B的接收情况可能是

Screen Shot 2017-04-17 at 9.59.03 PM.png

产生粘包问题的原因有多种,比如主机发送端buff粘包,接受端buff粘包以及网络传输中粘包,但是在这里纠结粘包原因已经没有特别明显的意义,重要的是如何在接收端解决粘包问题(有兴趣的同学可以自行研究原因)。


粘包问题的最本质原因在与接收对等方无法分辨消息与消息之间的边界在哪,那么很明显的我们需要知道一个消息包的长度是多少;除此之外还需要知道,这个是个什么消息,以及它长啥样,因此一个消息包种还需要消息ID 和消息内容2个元素,因此总共最少需要有3个元素:
msg_type
msg_lenth

msg_value
就是传统的tlv格式.

我们通过这3个元素,就可以解决TCP/IP协议的粘包问题,使接收端无论如何都可以识别来自发送端的消息内容(要彻底解决粘包,除了tlv,还需要校验码,怕发送端截断然后有巧合发生)。而NF中就是如此,使用这3个元素来解决TCP/IP的粘包问题。 有些同学可能会想,我使用有边界协议啊,比如udp 或者 websocket的frame。从设计上,这些协议是按照帧(包)发送的,自带边界,但是实际中并不那么如意。一个UDP包可以承载的传输内容为一个以太网(Ethernet)数据帧的MTU(最大传输单元),其中数据报的首部为20字节, IP数据报的数据区长度最大为1480字节,而这个1480字节就是用来放TCP传来的TCP报文段或UDP传来的UDP数据报的.   又因为UDP数据报的首部8字节,所以UDP数据报的数据区最大长度为1472字节.   这个1472字节就是我们可以使用的字节数,但是实际上,各路由器可能会设置这个MTU值为不同的值    有兴趣的同学可以自行去研究了。


2:大小端问题
所谓大小端是指 我们的数据在物理内存中存放和访问的顺序,因此再不同cpu上面可能表现为不同,有些语言天然处理好这些细节而无需处理,譬如说java;但是Cpper就需要自己去处理大小端问题。大多数情况下,网络字节序默认使用大端传输,因此client发送之前需要强制转换为大端发送,server收到后在转换为本地字节序进行处理。

如何解决?

NFIMsgHead类中有各种基础数据类型在各平台的大小端转换接口:
int64_t NF_HTONLL(int64_t nData)
int64_t NF_NTOHLL(int64_t nData)
int32_t NF_HTONL(int32_t nData)
int32_t NF_NTOHL(int32_t nData)
int16_t NF_HTONS(int16_t nData)
int16_t NF_NTOHS(int16_t nData)

我们可以使用这些数据转换接口来转换包头内容,而包体内容,则使用protocol-buf,pb内部自行解决了大小端问题,并跨各种语言。

3:protocolbuf

当前即时通讯应用中最热门的通信协议无疑就是Google的Protobuf了,基于它的优秀性能和内容压缩率之高,大量的游戏以及主流的应用也早已在使用它。NF使用的协议也是基于protocol buf,利用PB描述NF需要的基础协议,然后大量适用在NF各种通信协议中,包含客户端<->服务器,服务器<->服务器,数据库存储等内容.


关于选择pb的理由:
首先,pb有自动的打包解包库,并提供中间描述语言进行设计消息体,大大大提高工作效率;
其次在移动互联网时代,手机流量、电量是最为有限的资源,而移动端的通讯则必须得直面这两个问题。
解决流量过大最基本的方法就是压缩通信内容,而数据压缩后流量减小带来的自然结果也就是省电和节约带宽流量,而pb在压缩方面做的非常不错,能有效节约流量带宽等资源。

4:NF中的基础协议
基于NF中已经把所有数据抽象为几类基础数据,因此在再吧数据传输给客户端的时候只需要根据基础数据抽象基础通信协议即可。
比如 NF中通用属性数据有int, float(double实现),string,object,vector2,vector3。因此普通的消息内容根据这几类基础数据设计为如下结构体:
  1. message PropertyInt
  2. {
  3.     required bytes     property_name = 1;
  4.     required int64      data = 2;
  5. }

  6. message PropertyFloat
  7. {
  8.     required bytes     property_name = 1;
  9.     required float      data = 2;
  10. }

  11. message PropertyString
  12. {
  13.     required bytes     property_name = 1;
  14.     required bytes     data = 2;
  15. }

  16. message PropertyObject
  17. {
  18.     required bytes     property_name = 1;
  19.     required Ident      data = 2;
  20. }

  21. message PropertyVector2
  22. {
  23.     required bytes     property_name = 1;
  24.     required Vector2      data = 2;
  25. }

  26. message PropertyVector3
  27. {
  28.     required bytes     property_name = 1;
  29.     required Vector3      data = 2;
  30. }
复制代码

但是在一个用户数据信息内部,可能有大量的int类型数据或者string类型数据,因此我们还需要组合以上结构体,使其适用于复杂或者较多的数据传输,好在protocolbuf已经考虑到这一点:


message ObjectPropertyList
{
        required Ident  player_id = 1;
        repeated PropertyInt property_int_list = 2;
        repeated PropertyFloat property_float_list = 3;
        repeated PropertyString property_string_list = 4;
        repeated PropertyObject property_object_list = 5;
        repeated PropertyVector2 property_vector2_list = 6;
        repeated PropertyVector3 property_vector3_list = 7;
}

那么我们现在,ObjectPropertyList可以传输一切用户数据了。
当然,如果我们想一次性传输多个用户数据给客户端,则继续使用repeated关键字:
message MultiObjectPropertyList
{
        repeated ObjectPropertyList multi_player_property = 1;
}

同理,NF的另一种数据存储格式record也是利用protocol buf把数据类型划分为int, float(double实现),string,object,vector2,vector3等类型,再抽象成每一个record拥有多行数据,因此一个record二维表中定位数据主要是根据row 和col来定位,record的数据相对property较为复杂一些,因此计划放在后续的篇幅单独讲解。其实最关键是在于,以上协议的细节操作以及存储和通信打包解包,都已经有NF引擎代劳,使用者完全不用操心这些细节。

后续篇章我们会讲解,从如何启动服务器,到正常进入游戏的广播事项,以及创建NPC进行杀怪升级掉落。

NF项目为开源的分布式服务器解决方案,其中包含了网络库,actor库,以及数据驱动等新技术,能大幅提升开发效率节省开发周期以及提高程序的稳定性。
项目地址 https://github.com/ketoo/NoahGameFrame
如感觉对您有帮助,请给与star,同时也邀请广大同行参与开发和维护,作者QQ 342006,交流QQ群 341159815。
欢迎转载,转载请注明来源,本文版权归作者所有!



回复

使用道具 举报

您需要登录后才可以回帖 登录 | Register Now

本版积分规则

 

GMT+8, 2018-10-21 08:17 , Processed in 0.084703 second(s), 28 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表