Silverlight MMORPG WebGame游戏设计(二)--通讯协议之惑

         晚上看到我在silverlight webGame上的导师"深蓝色右手"拿到图片荣誉,觉得他名至实归。回想自己2010过年来到北京忙于一些琐屑的事情,好久没有动手写我的web传奇了。

         去年在群里说我过年要开源我的Web传奇,写一些服务端的文章。后来我发现自己在服务端开发上经验还欠缺,还没有一个成熟的框架,自己也在摸索中,我也打算今年向公司用C++写服务端的同事学下。

          在2009年10月份,我就打算写服务端,可我两眼一抹黑,用什么语言写,用什么通讯方式?作为一个6年来一直用C#的人来说,用C#进行开发是个风险比较小的方案。

          那么通讯方式呢?siverlight里客户端和服务端通讯有 WCF,RIA Services,Socket,Http。选那一个好呢?我们看以下的场景.

          图片

 

          这是令人热血沸腾的传奇3攻城画面,我在上一篇文章所写的攻城战就是这个场面,左下角的旗台也就是当年我誓死捍卫的阵地,想当年我“一夫当关,万夫莫开”,哎,不提当年了。

          我们数数这个画面的人物:50个人物,三种职业。有战士在释放“莲月剑法”,有法师在释放“爆裂火焰”,有道士在释放“复活术”。假如你是一个战士,正在用炼狱砍人。那么这个屏幕上的50个人都应该能看到你在砍人,当然说看到时通俗的说法,用专业术语来说是50个客户端同时收到了你砍人的动作数据。这是典型的服务端向客户端推送数据的过程。这一个过程持续时间很短,砍一下人不过1秒左右,为了做到及时性,50个客户端应当在100毫秒以内接收到数据才能保证游戏流畅度.

         这样以来,http通讯方式就被排除了,因为http方式服务端是没办法主动发送数据到客户端的,就如同你在浏览器里看网页一样,你请求什么页面,才会得什么内容。打个比方,如果你用http方式来玩游戏的话,你就得不停得刷新浏览器。当然你可能知道页面也能定时自动刷新的,那么这个定时器可是1秒要刷新100次以上。如果50个人都站着不动,那么就算没有数据要发送,服务端却要承受着:50*100=5000次请求/秒。所以我们需要的是服务端能即时得在需要的时候发数据主动发送到客户端。

       

        那么 WCF,RIA Services呢,在很多siverlight程序里都选用了WCF,RIA Services来传递数据,特别是管理系统,siverlight站点.但是做一个webGame是否可行?

        WCF是什么?简单介绍下:WCF整合了Remoting和webService等等。而且开发简单(相对的),它既弥补Remoting的只能在.net上运行的缺点,又弥补了asmx只能单向传送消息的缺点。也就是说,WCF是实现跨平台,在跨平台的同时,也可以双向通信.

        看来WCF通讯是可行的,但是WCF双工通讯(Duplex channel)又分为基于Http方式和TCP方式的。Http本质上是两对 Request/Response来模拟的,不算真正意义的Duplex channel,效率自然也不高。基于TCP方式的WCF通讯效率较高,也符合OO思想,但是为了追求更高的效率最好也不用它。因为WCF是比较高层的封装,对比更接近底层的Socket通讯,效率不够高。

       RIA Services本来就没有一个成熟的正式版本,后来微软也放弃更新了,把它和WCF整合了下,叫WCF RIA Services。

       口舌了这么多,其实我想要给大家介绍的重量级人物都等得不耐烦了,让我们请出它来吧:Socket,对,就是它,最早出现在UNIX系统上。后来出现在window系统上也叫做winsocket.这位兄弟在游戏界可是大名鼎鼎,什么“魔兽世界”,“传奇”都得用它。

   

       如何把单机版的SL游戏,变成网络版的?

   ①.我们需要写一个服务端,和SL客户端之间用Socket通讯

       引用命名空间:

       

 using System.Net.Sockets;
 
using System.Net;

 

       我们要新建两个项目:一个服务端使用的SocketServer项目,一个SL客户端使用的SocketClient.

      

     需要注意的是SocketClient是Silverlight类库项目,否则SL客户端无法引用。

      ②。我们需要一个安全策略文件,在SL3.0里微软为了解决跨域通讯的安全问题,强制要求服务端能提供安全策略文件clientaccesspolicy.xml。

        

代码
   <?xml version="1.0" encoding ="utf-8"?>
     
<access-policy>
        
<cross-domain-access>
            
<policy>
                
<allow-from>
                    
<domain uri="*" />
                
</allow-from>
                    
<grant-to>
                             
<socket-resource port="4502-4534" protocol="tcp" />
                             
<resource path="/clientbin"/>   
                        
</grant-to>
                  
</policy>
                
</cross-domain-access>
               
</access-policy>

 

 

        这里我们可以看到SL socket通讯使用的是tcp协议,端口限定在4502-4534之间.

     clientaccesspolicy.xml需要放在服务端

    ③服务端在和客户端首次通讯时,客户端会自动向服务端943端口发送安全策略文件的请求。服务端需要把clientaccesspolicy.xml里的内容以byte[]发送给SL客户端

       所以我们在启动服务端的时候,需要读取clientaccesspolicy.xml里的内容,同时监听943端口.

 

     

启动安全策略文件提供服务
   internal void StartupPolicyServer()
        {
            
string policyFile = Path.Combine(Application.StartupPath, "clientaccesspolicy.xml");//读取安全策略文件的内容放入policyBuffer  byte[]中

            
using (FileStream fs = new FileStream(policyFile, FileMode.Open, FileAccess.Read))
            {
                policyBuffer 
= new byte[fs.Length];
                fs.Read(policyBuffer, 
0, policyBuffer.Length);
            }
            
try
            {
                listener 
= new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                listener.Bind(
new IPEndPoint(IPAddress.Any, 943));//监听943端口
                listener.Listen(100);
                listener.BeginAccept(
new AsyncCallback(OnClientConnect), null);//当客户端访问943端口时回调OnClientConnect方法
            }
            
catch
            { 
            }
        }

        
//客户端访问943端口时

         
private readonly string policyRequestString = "<policy-file-request/>";

       
private void OnClientConnect(IAsyncResult result)
        {

             ....

             requestBuffer 
= new byte[policyRequestString.Length];

             client.BeginReceive(requestBuffer, 
0, policyRequestString.Length, SocketFlags.None, new AsyncCallback(OnReceive), client);//接收"<policy-file-request/>"内容长度的byte[]请求。

        }

  

         
//服务端开始接收客户端的数据

         
private void OnReceive(IAsyncResult result)
        {
            Socket client 
= result.AsyncState as Socket;
            
try
            {
                received 
+= client.EndReceive(result);//累加接收的字节数目,为了判断是否接收完
                if (received < policyRequestString.Length) //如果"<policy-file-request/>"内容长度的byte[]没有接收完继续接收
                {
                    client.BeginReceive(requestBuffer, received, policyRequestString.Length 
- received, SocketFlags.None, new AsyncCallback(OnReceive), client);
                    
return;
                }
                
string request = System.Text.Encoding.UTF8.GetString(requestBuffer, 0, received);
                
if (StringComparer.InvariantCultureIgnoreCase.Compare(request, policyRequestString) != 0//如果客户端在943端口发送的不是"<policy-file-request/>"内容的数据,也就是说请求的不是安全策略文件,就退出函数
                {
                    client.Close();
                    
return;
                }
                client.BeginSend(policyBuffer, 
0, policyBuffer.Length, SocketFlags.None, new AsyncCallback(OnSend), client);//否则就把安全策略文件的byte[]内容发送给客户端
            }

            
catch (SocketException)
            {
                client.Close();
            }
        }

 

       ④客户端接收到了安全策略文件就能继续在 4502-4534端口之间和服务端进行继续的通讯了,如果服务端没能发送安全策略文件的内容给客户端,则SL客户端会报一个“安全策略”的异常。

 

        由于文章篇幅已经比较长了,到此我们SL客户端和服务端的通讯可以进行,那么服务端和客户端的通讯内容如何约定呢?我们这样把游戏里的各种面向对象的数据以难以肉眼判读的byte[]字节发送到服务端呢?这就想我们把一纸洋洋洒洒的文章用粉碎机弄成纸屑,如果没有高强的法力,我们很难把纸屑还原成纸张.

       下一篇文件,我们会探讨下服务端和客户端如何组织通讯内容。Silverlight MMORG WebGame游戏设计(三)-----Server和Client的暗号

       上一篇文章:Silverlight MMORG WebGame游戏设计(一)-----一个游戏爱好者的webGame之路

 

posted @ 2010-04-07 23:09  王传炜  阅读(4246)  评论(15编辑  收藏  举报