程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> 一步一步開發Game服務器(三)加載腳本和服務器熱更新,game腳本

一步一步開發Game服務器(三)加載腳本和服務器熱更新,game腳本

編輯:C#入門知識

一步一步開發Game服務器(三)加載腳本和服務器熱更新,game腳本


大家可能對游戲服務器的運行不太理解或者說不太清楚一些機制。

但是大家一定會明白一點,當程序在運行的時候出現一些bug,必須及時更新,但是不能重啟程序的情況下。

這裡牽涉到一個問題。比如說在游戲裡面,,如果一旦開服,錯非完全致命性bug,否則是不能頻繁重啟服務器程序的,

你重啟一次就可能流失一部分玩家。那麼就牽涉到程序熱更新修復bug功能。

今天就來扒一扒熱更新的事情。

java和C#的加載機制有著一定的區別,java是吧.java的文件編譯成.class的文件進行加載的。而c#是把.cs的相關文件打包成DLL才能進行加載。

這樣導致的結果就是,java可以熱更新單個.class文件 而C#就只能做到加載DLL文件。

至於java的加載機制和代碼我就不在BB了,以後會發表相關文章。

今天只關注C#如何做到就行。

我們創建一個類庫項目 ClassLibraryMain

創建類 TestMain

    public class TestMain
    {
        public static string TestStr = "ssss";
    }

創建兩個接口

  public interface IScript2
    {
    }


   public interface IScript
    {
        string GetStr();
    }

創建類庫 ClassLibraryScript  然後添加引用 ClassLibraryMain

創建類 TestScript1

  public class TestScript1 : IScript, IScript2
    {
        public string GetStr()
        {
            return "我是《TestScript1》" + TestMain.TestStr;
        }
    }

 

創建類 TestScript

    public class TestScript : IScript
    {
        public TestScript()
        {
        }

        public string GetStr()
        {
            return "我是《TestScript》" + TestMain.TestStr;
        }
    }

創建一個解決方案文件夾 NewFolder1 在創建類 TestScript

    public class TestScript : IScript
    {
        public TestScript()
        {
        }

        public string GetStr()
        {
            return "我是《ClassLibraryScript.NewFolder1.TestScript》" + TestMain.TestStr;
        }
    }

 

准備工作完成,接下來分析一下C#的加載

C#下動態加載類,那麼需要利用System.Reflection 空間下面的反射,才能完成對DLL的加載

Assembly 對象,是反射。

Assembly.LoadFrom(string path);//加載DLL或者EXE程序

Assembly.GetExportedTypes();獲取程序集中所有的類型,

Type.GetInterfaces();獲取一個類型的所有繼承和實現的接口對象;

 

創建 LoadScriptManager 類

 1 /// <summary>
 2     /// 只支持加載一個DLL,
 3     /// </summary>
 4     public class LoadScriptManager
 5     {
 6         private static readonly LoadScriptManager instance = new LoadScriptManager();
 7         public static LoadScriptManager GetInstance { get { return instance; } }
 8 
 9         private Dictionary<string, List<object>> Instances = new Dictionary<string, List<object>>();
10 
11         /// <summary>
12         /// 
13         /// </summary>
14         /// <param name="pathName">文件路徑,包含名稱。dll, exe</param>
15         public void Load(string pathName)
16         {
17             GC.Collect();
18             Assembly assembly = Assembly.LoadFrom(pathName);
19             Type[] instances = assembly.GetExportedTypes();
20             Dictionary<string, List<object>> tempInstances = new Dictionary<string, List<object>>();
21             foreach (var itemType in instances)
22             {
23 #if DEBUG
24                 Console.Write(itemType.Name);
25 #endif
26                 Type[] interfaces = itemType.GetInterfaces();
27                 object obj = Activator.CreateInstance(itemType);
28                 foreach (var iteminterface in interfaces)
29                 {
30 #if DEBUG
31                     Console.Write(": " + iteminterface.Name);
32 #endif
33                     if (!tempInstances.ContainsKey(iteminterface.Name))
34                     {
35                         tempInstances[iteminterface.Name] = new List<object>();
36                     }
37                     tempInstances[iteminterface.Name].Add(obj);
38                 }
39 #if DEBUG
40                 Console.WriteLine();
41 #endif
42             }
43             lock (Instances)
44             {
45                 Instances = tempInstances;
46             }
47         }
48 
49         /// <summary>
50         /// 根據名稱查找實例
51         /// </summary>
52         /// <param name="name"></param>
53         /// <returns></returns>
54         public List<object> GetInstances(string name)
55         {
56             lock (Instances)
57             {
58                 if (Instances.ContainsKey(name))
59                 {
60                     return new List<object>(Instances[name]);
61                 }
62             }
63             return null;
64         }        
65     }

 

接下來我們測試一下,

創建一個控制台程序,然後添加引用 ClassLibraryMain 把ClassLibraryScript的DLL文件拷貝到控制台程序的DEBUG目錄下面,或者其他目錄,我是放在DEBUG目錄下的

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             GC.Collect();
 6             LoadScriptManager.GetInstance.Load("ClassLibraryScript.dll");
 7             List<object> instances = LoadScriptManager.GetInstance.GetInstances(typeof(IScript).Name);
 8             if (instances != null)
 9             {
10                 foreach (var item in instances)
11                 {
12                     if (item is IScript)
13                     {
14                         Console.WriteLine(((IScript)item).GetStr());
15                     }
16                 }
17             }
18             Console.ReadLine();
19         }
20     }

輸出:

TestScript: IScript
TestScript: IScript
TestScript1: IScript: IScript2
我是《ClassLibraryScript.NewFolder1.TestScript》ssss
我是《TestScript》ssss
我是《TestScript1》ssss

 為了得到熱更新效果,我們修改一下程序

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             while (true)
 6             {
 7                 GC.Collect();
 8                 TestMain.TestStr = Console.ReadLine();
 9                 LoadScriptManager.GetInstance.Load("ClassLibraryScript.dll");
10                 List<object> instances = LoadScriptManager.GetInstance.GetInstances(typeof(IScript).Name);
11                 if (instances != null)
12                 {
13                     foreach (var item in instances)
14                     {
15                         if (item is IScript)
16                         {
17                             Console.WriteLine(((IScript)item).GetStr());
18                         }
19                     }
20                 }
21             }
22             Console.ReadLine();
23         }
24     }

 

 

第一次加載
TestScript: IScript
TestScript: IScript
TestScript1: IScript: IScript2
我是《ClassLibraryScript.NewFolder1.TestScript》第一次加載
我是《TestScript》第一次加載
我是《TestScript1》第一次加載

 

 當我們嘗試去更新文件才發現,根本沒辦法更新,

如何解決文件的獨占問題呢?

 查看 Assembly 發現一個可以使用字節流數組加載對象,

接下來修改一下 load方法

 1 public void Load(string pathName)
 2         {
 3             Dictionary<string, List<object>> tempInstances = new Dictionary<string, List<object>>();
 4             try
 5             {
 6                 GC.Collect();
 7                 byte[] bFile = null;
 8                 using (FileStream fs = new FileStream(pathName, FileMode.Open, FileAccess.Read))
 9                 {
10                     using (BinaryReader br = new BinaryReader(fs))
11                     {
12                         bFile = br.ReadBytes((int)fs.Length);
13                         Assembly assembly = Assembly.Load(bFile);
14                         Type[] instances = assembly.GetExportedTypes();
15                         foreach (var itemType in instances)
16                         {
17 #if DEBUG
18                             Console.Write(itemType.Name);
19 #endif
20                             Type[] interfaces = itemType.GetInterfaces();
21                             object obj = Activator.CreateInstance(itemType);
22                             foreach (var iteminterface in interfaces)
23                             {
24 #if DEBUG
25                                 Console.Write(": " + iteminterface.Name);
26 #endif
27                                 if (!tempInstances.ContainsKey(iteminterface.Name))
28                                 {
29                                     tempInstances[iteminterface.Name] = new List<object>();
30                                 }
31                                 tempInstances[iteminterface.Name].Add(obj);
32                             }
33 #if DEBUG
34                             Console.WriteLine();
35 #endif
36                         }
37                     }
38                 }
39             }
40             catch (Exception ex)
41             {
42                 Console.WriteLine("加載文件拋錯" + ex);
43             }
44             Instances = tempInstances;
45         }

運行一下效果

第一次
TestScript: IScript
TestScript: IScript
TestScript1: IScript: IScript2
我是《ClassLibraryScript.NewFolder1.TestScript》第一次
我是《TestScript》第一次
我是《TestScript1》第一次

 

接下來我們修改一下  TestScript1 腳本文件 

 

    public class TestScript1 : IScript, IScript2
    {
        public string GetStr()
        {
            return "我是《TestScript1》 我是修改過後的 " + TestMain.TestStr;
        }
    }

然後編譯生成一次

這下就看到了,我們程序熱更新了,,

需要注意的是,C#依然可以做到更新單個文件,但是都必須打包成DLL,和java更新單個文件必須編譯成.class文件一樣。

目前,這個方式,實現的加載dll腳本,。但是沒有做加載後dll動態數據保存。這個比較復雜。

我們這裡的創建了三個項目,分別為, ConsoleApplication5 控制台, ClassLibraryMain 類庫  ClassLibraryScript 類庫,

引用關系為,ConsoleApplication5和ClassLibraryScript 引用了ClassLibraryMain 類庫,

ClassLibraryScript 可以調用 ClassLibraryMain 庫中保存的數據,

ClassLibraryScript 類庫僅僅是腳本。也就是說,通常可以把業務邏輯處理模塊獨立到這個庫中,完成業務邏輯。不牽涉數據保存。

這樣就能完全滿足程序的熱更新,不必重啟程序,達到了修改邏輯bug目的。

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved