zoukankan      html  css  js  c++  java
  • 一步一步开发Game服务器(三)加载脚本和服务器热更新(二)完整版

    上一篇文章我介绍了如果动态加载dll文件来更新程序 一步一步开发Game服务器(三)加载脚本和服务器热更新

    可是在使用过程中,也许有很多会发现,动态加载dll其实不方便,应为需要预先编译代码为dll文件。便利性不是很高。

    那么有么有办法能做到动态实时更新呢????

    官方提供了这两个对象,动态编译源文件。

    提供对 C# 代码生成器和代码编译器的实例的访问。 CSharpCodeProvider
    提供一下方法加载源文件,
    
    
    // 基于包含在 System.CodeDom.CodeCompileUnit 对象的指定数组中的 System.CodeDom 树,使用指定的编译器设置编译程序集。
    public virtual CompilerResults CompileAssemblyFromDom(CompilerParameters options, params CodeCompileUnit[] compilationUnits);
     
    // 从包含在指定文件中的源代码,使用指定的编译器设置编译程序集。
    public virtual CompilerResults CompileAssemblyFromFile(CompilerParameters options, params string[] fileNames);
    
    // 从包含源代码的字符串的指定数组,使用指定的编译器设置编译程序集。
    public virtual CompilerResults CompileAssemblyFromSource(CompilerParameters options, params string[] sources);

    上面的方法我测试了CompileAssemblyFromFile  CompileAssemblyFromSource 两个方法,

    CompileAssemblyFromFile  的意思给定文件路径去编译源文件,可以直接加入调试信息,调试,

    CompileAssemblyFromSource 的意思给定源码类容去编译源文件,无法直接加入调试信息,需要加入  System.Diagnostics.Debugger.Break(); 在源文件插入断点调试。但是在除非断点的时候会弹出对话框,跳转指定源文件附近才能调试。略微麻烦。

    以上两种方法需要调试都需要下面的调试参数配合 IncludeDebugInformation = true; 才能有用

    表示用于调用编译器的参数。 CompilerParameters
    提供一下参数
    
    //不输出编译文件
    parameter.GenerateExecutable = false;
    //生成调试信息
    parameter.IncludeDebugInformation = true;
    //不输出到内存
    parameter.GenerateInMemory = false;
    //添加需要的程序集
    parameter.ReferencedAssemblies.AddRange(tempDllNames);

    废话不多说,我们来测试一下代码

    首先创建一个 LoadManager.cs

    public class LoadManager
        {
            private static LoadManager instance = new LoadManager();
            public static LoadManager GetInstance { get { return instance; } }
    
            public void LoadFile(string path)
            {
                CSharpCodeProvider provider = new CSharpCodeProvider();
                CompilerParameters parameter = new CompilerParameters();
                //不输出编译文件
                parameter.GenerateExecutable = false;
                //生成调试信息
                parameter.IncludeDebugInformation = true;
                //不输出到内存
                parameter.GenerateInMemory = false;
                //添加需要的程序集
                parameter.ReferencedAssemblies.Add("System.dll");
                //编译文件
                Console.WriteLine("动态加载文件:" + path);
                CompilerResults result = provider.CompileAssemblyFromFile(parameter, path);//根据制定的文件加载脚本
                //判断是否有错误
                if (!result.Errors.HasErrors)
                {
                    //获取加载的所有对象模型
                    Type[] instances = result.CompiledAssembly.GetExportedTypes();
                    foreach (var itemType in instances)
                    {
                        Console.WriteLine("生成实例:" + itemType.Name);
                        //生成实例
                        object obj = Activator.CreateInstance(itemType);
                    }
                }
                else
                {
                    var item = result.Errors.GetEnumerator();
                    while (item.MoveNext())
                    {
                        Console.WriteLine("动态加载文件出错了!" + item.Current.ToString());
                    }
                }
            }
        }

    创建测试源文件 TestCode.cs

    public class TestCode
        {
            public TestCode()
            {
                Console.WriteLine("我是TestCode");
            }
        }

    调用测试

    string cspath = @"F:javatestConsoleApplication6CodeDomeCodeTestCode.cs";
                LoadManager.GetInstance.LoadFile(cspath);

    输出结果 表面我们加载成功了,

    动态加载文件:F:javatestConsoleApplication6CodeDomeCodeTestCode.cs
    生成实例:TestCode
    我是TestCode

    接下来我们

    修改一下 TestCode.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace CodeDomeCode
    {
        public class TestCode
        {
            public TestCode()
            {
                Console.WriteLine("我是TestCode");
            }
        }
    }

    结果程序输出错误

    动态加载文件:F:javatestConsoleApplication6CodeDomeCodeTestCode.cs
    动态加载文件出错了!f:javatestConsoleApplication6CodeDomeCodeTestCode.cs(3,14) : error CS0234: 命名空间“System”中不存在类型或命名空间名称“Linq”(是否缺少程序集引用?)

    这就出现了一个问题,

    //添加需要的程序集
                parameter.ReferencedAssemblies.Add("System.dll");

    我们的编译参数。附件编译依赖程序集的只添加了 System.dll 文件,所有导致编译出错。

    那么我们知道思考一个问题,这个依赖程序集,必须要手动添加嘛?是不是太费事 ?

    如果是做公共模块的话。我这么知道需要哪些依赖程序集呢?

    系统提供了AppDomain.CurrentDomain.GetAssemblies();获取当前程序集所有程序集

    Assembly.GetModules();程序集依赖项;

    既然这样,我们是不是可以依赖当前应用程序域加载呢?

    修改一下依赖程序集添加方式

    HashSet<String> ddlNames = new HashSet<string>();
                var asss = AppDomain.CurrentDomain.GetAssemblies();
                foreach (var item in asss)
                {
                    foreach (var item222 in item.GetModules(false))
                    {
                        ddlNames.Add(item222.FullyQualifiedName);
                    }
                }
    
                //添加需要的程序集
                parameter.ReferencedAssemblies.AddRange(ddlNames.ToArray());

    编译完成,依赖于依赖当前应用程序域加载依赖程序集;(需要注意的时候你的代码里面依赖的程序集,当前应用程序域也需要加载)

    动态加载文件:F:javatestConsoleApplication6CodeDomeCodeTestCode.cs
    生成实例:TestCode
    我是TestCode

    接下来我们看看如何才能加入调试情况呢?

    有两个问题,如果要加入调试,需要修改两个参数才能加入断点调试

    //生成调试信息
    parameter.IncludeDebugInformation = true;
    //输出编译对象到内存
    parameter.GenerateInMemory = true;

    在代码中直接加入断点测试

    运行起来

    进入断点调试了。

    如果是源文件是文本文件但是需要加入调试的话;

    System.Diagnostics.Debugger.Break();

    我们看到加入了调试了,两种方式都能加入调试信息;

    问题继续出现,我们在加载源文件的时候,需求里面肯定存在更新所加载的源文件吧。

    而且加载的文件对象肯定需要保存提供调用;

    修改程序。

    使用线程安全的集合保存所加载的实例对象

     ConcurrentDictionary<string, ConcurrentDictionary<string, object>> Instances = new ConcurrentDictionary<string, ConcurrentDictionary<string, object>>();
    //获取加载的所有对象模型
                Type[] instances = assembly.GetExportedTypes();
                foreach (var itemType in instances)
                {
                    //获取单个模型的所有继承关系和接口关系
                    Type[] interfaces = itemType.GetInterfaces();
                    //生成实例
                    object obj = Activator.CreateInstance(itemType);
                    foreach (var iteminterface in interfaces)
                    {
                        //判断是否存在键
                        if (!Instances.ContainsKey(iteminterface.Name))
                        {
                            Instances[iteminterface.Name] = new ConcurrentDictionary<string, object>();
                        }
                        //无加入对象,有更新对象
                        Instances[iteminterface.Name][itemType.Name] = obj;
                    }
                }

    把对象加入到集合中

    /// <summary>
            /// 返回查找的脚本实例
            /// </summary>
            /// <typeparam name="T">类型</typeparam>
            /// <returns></returns>
            public IEnumerable<T> GetInstances<T>()
            {
                //使用枚举迭代器,避免了再一次创建对象
                string typeName = typeof(T).Name;
                if (Instances.ContainsKey(typeName))
                {
                    foreach (var item in Instances[typeName])
                    {
                        if (item.Value is T)
                        {
                            yield return (T)item.Value;
                        }
                    }
                }
            }

    最后附加全套源码

    提供 源文件 .cs  和程序集加载 .dll

    提供支持路径递归加载和指定文件加载方式,并且提供了后缀筛选和附加dll加载。

    using Microsoft.CSharp;
    using System;
    using System.CodeDom.Compiler;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    
    /**
     * 
     * @author 失足程序员
     * @Blog http://www.cnblogs.com/ty408/
     * @mail 492794628@qq.com
     * @phone 13882122019
     * 
     */
    namespace Sz.Network.LoadScriptPool
    {
    
        /// <summary>
        /// 加载脚本文件
        /// </summary>
        public class LoadScriptManager
        {
    
            private static LoadScriptManager instance = new LoadScriptManager();
            public static LoadScriptManager GetInstance { get { return instance; } }
    
            HashSet<String> ddlNames = new HashSet<string>();
    
            LoadScriptManager()
            {
                var asss = AppDomain.CurrentDomain.GetAssemblies();
                foreach (var item in asss)
                {
                    foreach (var item222 in item.GetModules(false))
                    {
                        ddlNames.Add(item222.FullyQualifiedName);
                    }
                }
            }
    
            #region 返回查找的脚本实例 public IEnumerable<T> GetInstances<T>()
            /// <summary>
            /// 返回查找的脚本实例
            /// </summary>
            /// <typeparam name="T">类型</typeparam>
            /// <returns></returns>
            public IEnumerable<T> GetInstances<T>()
            {
                //使用枚举迭代器,避免了再一次创建对象
                string typeName = typeof(T).Name;
                if (Instances.ContainsKey(typeName))
                {
                    foreach (var item in Instances[typeName])
                    {
                        if (item.Value is T)
                        {
                            yield return (T)item.Value;
                        }
                    }
                }
            }
    
            ConcurrentDictionary<string, ConcurrentDictionary<string, object>> Instances = new ConcurrentDictionary<string, ConcurrentDictionary<string, object>>();
    
            #endregion
    
            #region 根据指定的文件动态编译获取实例 public void LoadCSharpFile(string[] paths, List<String> extensionNames, params string[] dllName)
            /// <summary>
            /// 根据指定的文件动态编译获取实例
            /// <para>如果需要加入调试信息,加入代码 System.Diagnostics.Debugger.Break();</para>
            /// <para>如果传入的是目录。默认只会加载目录中后缀“.cs”文件</para>
            /// </summary>
            /// <param name="paths">
            /// 可以是目录也可以是文件路径
            /// </param>
            /// <param name="dllName">加载的附加DLL文件的路径,绝对路径</param>
            public void LoadCSharpFile(string[] paths, params string[] dllName)
            {
                LoadCSharpFile(paths, null, dllName);
            }
    
            List<String> csExtensionNames = new List<String>() { ".cs" };
    
            /// <summary>
            /// 根据指定的文件动态编译获取实例
            /// <para>如果需要加入调试信息,加入代码 System.Diagnostics.Debugger.Break();</para>
            /// <para>如果传入的是目录。默认只会加载目录中后缀“.cs”文件</para>
            /// </summary>
            /// <param name="paths">
            /// 可以是目录也可以是文件路径
            /// </param>
            /// <param name="extensionNames">需要加载目录中的文件后缀</param>
            /// <param name="dllName">加载的附加DLL文件的路径,绝对路径</param>
            public void LoadCSharpFile(string[] paths, List<String> extensionNames, params string[] dllName)
            {
                GC.Collect();
                if (extensionNames == null)
                {
                    extensionNames = csExtensionNames;
                }
                foreach (var item in dllName)
                {
                    ddlNames.Add(item);
                }
                foreach (var item in ddlNames)
                {
                    Console.WriteLine("加载依赖程序集:" + item);
                }
                List<String> fileNames = new List<String>();
                ActionPath(paths, extensionNames, ref fileNames);
                string[] tempDllNames = ddlNames.ToArray();
                foreach (var path in fileNames)
                {
                    CSharpCodeProvider provider = new CSharpCodeProvider();
                    CompilerParameters parameter = new CompilerParameters();
                    //不输出编译文件
                    parameter.GenerateExecutable = false;
                    //生成调试信息
                    parameter.IncludeDebugInformation = true;
                    //需要调试必须输出到内存
                    parameter.GenerateInMemory = true;
                    //添加需要的程序集
                    parameter.ReferencedAssemblies.AddRange(tempDllNames);
                    //编译文件
                    Console.WriteLine("动态加载文件:" + path);
                    CompilerResults result = provider.CompileAssemblyFromFile(parameter, path);//根据制定的文件加载脚本
                    if (result.Errors.HasErrors)
                    {
                        var item = result.Errors.GetEnumerator();
                        while (item.MoveNext())
                        {
                            Console.WriteLine("动态加载文件出错了!" + item.Current.ToString());
                        }
                    }
                    else
                    {
                        ActionAssembly(result.CompiledAssembly);
                    }
                }
            }
            #endregion
    
            #region 根据指定的文件动态编译获取实例 public void LoadDll(string[] paths)
    
            List<String> dllExtensionNames = new List<String>() { ".dll", ".DLL" };
    
            /// <summary>
            /// 根据指定的文件动态编译获取实例
            /// <para>如果需要加入调试信息,加入代码 System.Diagnostics.Debugger.Break();</para>
            /// </summary>
            /// <param name="paths">
            /// 可以是目录也可以是文件路径
            /// <para>如果传入的是目录。只会加载目录中后缀“.dll”,“.DLL”文件</para>
            /// </param>
            public void LoadDll(string[] paths)
            {
                GC.Collect();
                List<String> fileNames = new List<String>();
                ActionPath(paths, dllExtensionNames, ref fileNames);
                byte[] bFile = null;
                foreach (var path in fileNames)
                {
                    try
                    {
                        Console.WriteLine("动态加载文件:" + path);
                        using (FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                        {
                            using (BinaryReader br = new BinaryReader(fs))
                            {
                                bFile = br.ReadBytes((int)fs.Length);
                                ActionAssembly(Assembly.Load(bFile));
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("动态加载文件:" + ex);
                    }
                }
            }
            #endregion
    
            #region 处理加载出来的实例 void ActionAssembly(Assembly assembly)
            /// <summary>
            /// 处理加载出来的实例
            /// </summary>
            /// <param name="assembly"></param>
            void ActionAssembly(Assembly assembly)
            {
                ConcurrentDictionary<string, ConcurrentDictionary<string, object>> tempInstances = new ConcurrentDictionary<string, ConcurrentDictionary<string, object>>();
                //获取加载的所有对象模型
                Type[] instances = assembly.GetExportedTypes();
                foreach (var itemType in instances)
                {
                    //获取单个模型的所有继承关系和接口关系
                    Type[] interfaces = itemType.GetInterfaces();
                    //生成实例
                    object obj = Activator.CreateInstance(itemType);
                    foreach (var iteminterface in interfaces)
                    {
                        //加入对象集合
                        if (!Instances.ContainsKey(iteminterface.Name))
                        {
                            tempInstances[iteminterface.Name] = new ConcurrentDictionary<string, object>();
                        }
                        tempInstances[iteminterface.Name][itemType.Name] = obj;
                    }
                }
                Instances = tempInstances;
            } 
            #endregion
    
            #region 处理传入的路径 void ActionPath(string[] paths, List<String> extensionNames, ref List<String> fileNames)
            /// <summary>
            /// 处理传入的路径,
            /// </summary>
            /// <param name="paths"></param>
            /// <param name="extensionNames"></param>
            /// <param name="fileNames"></param>
            void ActionPath(string[] paths, List<String> extensionNames, ref List<String> fileNames)
            {
                foreach (var path in paths)
                {
                    if (System.IO.Path.HasExtension(path))
                    {
                        if (System.IO.File.Exists(path))
                        {
                            fileNames.Add(path);
                        }
                        else
                        {
                            Console.WriteLine("动态加载 无法找到文件:" + path);
                        }
                    }
                    else
                    {
                        GetFiles(path, extensionNames, ref fileNames);
                    }
                }
            }
            #endregion
    
            #region 根据指定文件夹获取指定路径里面全部文件 void GetFiles(string sourceDirectory, List<String> extensionNames, ref  List<String> fileNames)
            /// <summary>
            /// 根据指定文件夹获取指定路径里面全部文件
            /// </summary>
            /// <param name="sourceDirectory">目录</param>
            /// <param name="extensionNames">需要获取的文件扩展名</param>
            /// <param name="fileNames">返回文件名</param>
            void GetFiles(string sourceDirectory, List<String> extensionNames, ref  List<String> fileNames)
            {
                if (!Directory.Exists(sourceDirectory))
                {
                    return;
                }
                {
                    //获取所有文件名称
                    string[] fileName = Directory.GetFiles(sourceDirectory);
                    foreach (string path in fileName)
                    {
                        if (System.IO.File.Exists(path))
                        {
                            string extName = System.IO.Path.GetExtension(path);
                            if (extensionNames.Contains(extName))
                            {
                                fileNames.Add(path);
                            }
                            else
                            {
                                Console.WriteLine("无法识别文件:" + path);
                            }
                        }
                        else
                        {
                            Console.WriteLine("动态加载 无法找到文件:" + path);
                        }
                    }
                }
                //拷贝子目录       
                //获取所有子目录名称
                string[] directionName = Directory.GetDirectories(sourceDirectory);
                foreach (string directionPath in directionName)
                {
                    //递归下去
                    GetFiles(directionPath, extensionNames, ref fileNames);
                }
            }
            #endregion
        }
    }
    View Code
  • 相关阅读:
    【算法笔记】B1020 月饼
    JZOJ 3412. 【NOIP2013模拟】KC看星
    JZOJ 3517. 空间航行
    JZOJ 3515. 软件公司
    JZOJ 3514. 最小比例
    JZOJ 3490. 旅游(travel)
    luogu P3178 [HAOI2015]树上操作
    JZOJ 3427. 归途与征程
    JZOJ 3426. 封印一击
    JZOJ 3425. 能量获取
  • 原文地址:https://www.cnblogs.com/shizuchengxuyuan/p/4503917.html
Copyright © 2011-2022 走看看