随笔 - 119  文章 - 290  trackbacks - 0

博客搬家了哦,请移步
叫我abc

常用链接

留言簿(12)

随笔分类

我的博客

搜索

  •  

积分与排名

  • 积分 - 301934
  • 排名 - 84

最新评论

阅读排行榜

近些天写了一些游戏逻辑,发现项目组在写C/S网络消息包的时候,根据作用的不同,定义了很多的不同的消息格式。比如,所有的消息包都继承自下面这个基本结构。
struct SPacket
{
  
byte  bType;
}
然后,添加物品的消息包,设置成员状态的消息包都分别定义了不同的结构。
struct SAddItemPacket : public SPacket
{
  SItem item;
  SAddItem()
  
{
    bType 
= STC_AddItem;
  }

}


struct SSetMemberPacket : public  SPacket
{
  
byte  bStatus;
  SSetMemberPacket()
  
{
    bType 
= STC_SetMember;
  }

}

随着项目填充越来越多的内容,消息包的类型越来越多,不能避免吗?

晚上睡觉的时候,想着如果把网络引擎引入项目之类的会怎样呢?然后想到看过的ICE教程,就大概的思考类似ICE这样的通用RPC大概是怎样实现的。
RPC由服务对象以及其客户端的表现——代理对象构成。客户端代码中,调用了代理对象的某个接口,就会导致其服务对象执行该接口的实现,使得客户端看起来就像是执行本地调用一样。
因此RPC的直观概念是接口调用的网络映射,核心内容是:如何将客户端调用(某个对象的)某个接口的这一行为进行通用的序列化?
对调用行为序列化和对对象序列化是不同的,而且RPC的目标是对任何类型的接口调用行为都能有一个通用的序列化方案。
我们要调用某个接口的时候,下达的命令通常是,“某某类型的,那个谁,把你的那个名叫什么的方法调用一下,参数是这些,a,b,c,d”。这样我们就进行了一次接口调用。因此,调用行为最基本的序列化方法是,类型ID+方法ID+对象ID+参数。将这些数据加成起来,就能作为一个调用消息包由客户端发向服务器,服务器处理RPC管理的对象将会根据类ID,对象ID找到服务对象,然后根据方法ID,找到方法,并能根据方法知道有几个参数,以及参数的类型,最终执行调用。

因此,我此时觉得,像项目组现在定义的多种大小不变,种类繁多的消息格式,其实都可以用一个能序列化调用行为的消息包来解决。该消息包的结构是:

class  SPacket
{
  WORD  wClassID;
  WORD  wMemberFuncID;
  BYTE  bParam[PACKET_PARAM_BUFFER_SIZE];
  QWORD  qwBufferOffset 
= 0;

public:
  SPacket(WORD par_wClassID,WORD par_FuncID, DWORD dwObjID);
  SPacket(WORD par_wClassID,WORD par_FuncID, QWORD qwObjID);

  template
<typename T>
  
bool  Push(T value)
  
{
    memcpy(bParam 
+ qwBufferOffset,value,sizeof(T);
    qwBufferOffset 
+= sizeof(T);
  }


  
bool  Pop(T &value)
  
{
    T  
*pValue = static_cast<T*>(bParam + qwBufferOffset)
    value 
= *pValue;
    qwBufferOffset 
+= sizeof(T);
  }


  QWORD  Size() 
const
  
{
    
return  sizeof(wClassID) + sizeof(wMemberFuncID) + qwBufferOffset;
  }

}
;
参数从左到右装入消息包,相应的也是从左到右从消息包取出,来配合服务器端执行相应的函数。
posted on 2007-01-26 23:26 LOGOS 阅读(2488) 评论(12)  编辑 收藏 引用

FeedBack:
# re: 通用网络消息包 2007-01-27 09:52 李锦俊
那把SPacket装包之后,发送到接受端,接受端还是要靠一个类型ID来区分不同的网络消息的。
是不是这样用?
class SSetMemberPacket : public SPacket
{
public:
SSetMemberPacket()
{
Push(STC_SetMember);
}
};  回复  更多评论
  
# re: 通用网络消息包 2007-01-27 11:12 LOGOS
不是。我觉得不要再增加更多的消息包结构体了,不然文件我看了会真的受不了的。
比如我要调用服务器 SomeClass::SetMember,我会这样
SPacket packet(SomeClassID,FUNC_SetMember,objID);
// push param
packet.Push(playerID);
packet.Push(status);
net.Send(socket,&packet.packet.Size());  回复  更多评论
  
# re: 通用网络消息包 2007-01-27 11:17 LOGOS
接受端区分消息类型肯定还是要ID啊。
也只有这样才能区分消息吧。这也是没有办法的事情。
但是,消息包的类型不用增加,
因为SPacket::SPacket(BYTE *bBuffer, DWORD dwSize)
可以将网络字节流变成SPacket对象。  回复  更多评论
  
# re: 通用网络消息包 2007-01-29 09:15 netdigger
不过这样可能会对运行速度,传输速度会有一定的影响  回复  更多评论
  
# re: 通用网络消息包 2007-01-29 09:37 李锦俊
其实这就是一个序列化的过程。
我以前用MFC的CArchive和CMemFile实现的也蛮好。  回复  更多评论
  
# re: 通用网络消息包 2007-01-29 09:37 李锦俊
另外说说,boost也有序列化的封装  回复  更多评论
  
# re: 通用网络消息包 2007-02-01 17:37 christanxw0
@LOGOS
这样你得严格控制push的顺序啊。如果定义消息结构显然字段的顺序编译期就确定下来了。但是你这么做,就必须在写代码的时候严格控制push的顺序,保证和接受方pop的顺序是一样的。  回复  更多评论
  
# re: 通用网络消息包 2007-02-02 20:22 LOGOS
@christanxw0
嗯。确实是这样子。
但是有得有失吧。
毕竟项目里面的消息包类型实在太庞大了,而且消息包以功能为向导定义的,如果缺少注释其含义通常会比较含糊。
  回复  更多评论
  
# re: 通用网络消息包 2007-11-20 09:20 ulti
可以用MQ4CCP的,感觉不错,不过没用过。
http://www.sixtyfourbit.org/mq4cpp.htm
  回复  更多评论
  
# re: 通用网络消息包 2008-01-03 10:31 abc
这么做有那些优点呢?  回复  更多评论
  
# re: 通用网络消息包 2008-01-27 13:59 okmmno1
复杂消息的序列化无法处理,变长消息处理不好。
总之,序列化是个比较复杂的问题, 正在头疼中。  回复  更多评论
  
# re: 通用网络消息包 2008-09-16 10:32 zuhd
可以尝试重载》和《哦,只要知道数据的长度就可以用memcpy了,不过像vector这样的就要把长度也序列化进去了,取出来的时候就直接放在对应的结构体了,非常方便,两边的对称也很有美感  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理