浏览代码

多人副本server初版

王刚 3 年之前
父节点
当前提交
619cca399d

+ 1 - 1
CSserver/Lib1/Config.cs

@@ -22,7 +22,7 @@ using StackExchange.Redis;
         {
             get
             {
-                var host = "192.168.10.86";
+                var host = "192.168.10.87";
                 if (GameOnline)
                 {
                     host = "115.159.121.129";

+ 13 - 0
CSserver/MultiDup/MultiDup.csproj

@@ -0,0 +1,13 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <OutputType>Exe</OutputType>
+    <TargetFramework>net5.0</TargetFramework>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\Lib1\Lib1.csproj" />
+    <ProjectReference Include="..\PBReferens\PBReferens.csproj" />
+  </ItemGroup>
+
+</Project>

+ 63 - 0
CSserver/MultiDup/Program.cs

@@ -0,0 +1,63 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading.Tasks;
+using System.Diagnostics;
+
+
+namespace MultiDup
+{
+    internal class Program
+    {
+        static Socket SocketWatch = null;                                       // 创建一个和客户端通信的套接字 
+        const int port = 6004;                                                  // 端口号(用来监听的) 
+        static void Main(string[] args)
+        {
+            selfTest();
+            IPAddress ip = IPAddress.Any;
+            IPEndPoint ipe = new IPEndPoint(ip, port);                         //将IP地址和端口号绑定到网络节点point上  
+
+            //定义一个套接字用于监听客户端发来的消息,包含三个参数(IP4寻址协议,流式连接,Tcp协议)  
+            SocketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+            SocketWatch.Bind(ipe);                                             // 监听绑定的网络节点           
+            SocketWatch.Listen(20);                                            // 将套接字的监听队列长度限制为20    
+
+            Console.WriteLine("开启监听......");
+            Console.WriteLine("ctrl + c 退出程序......");
+
+            Task.Run(WatchConnecting).Wait();                                  // 开启监听线程
+            SocketWatch.Close();                                               // 结束监听socket
+
+        }
+
+
+        static void selfTest()
+        {
+            var rdb = Redis.Ins.GetDatabase(0);
+            Debug.Assert(rdb.StringSet("test", "wanggang" + DateTime.Now.ToString("yyyyMMddHHmmss")), "Redis读写失败!");
+        }
+
+        /// <summary>
+        /// 监听客户端发来的请求  
+        /// </summary>
+        async static Task WatchConnecting()
+        {
+            while (true)                                                                 // 持续不断监听客户端发来的请求     
+            {
+                Socket connection;
+                try
+                {
+                    connection = await SocketWatch.AcceptAsync();
+                }
+                catch (Exception ex)
+                {
+                    Console.WriteLine(ex.Message);                                       // 提示套接字监听异常     
+                    break;
+                }
+
+                Lobby.Instance.OnNewPeerConnected(new Peer(connection));                 // 有新客户端连入
+            }
+        }
+
+    }
+}

+ 847 - 0
CSserver/MultiDup/db/MysqlUtil.cs

@@ -0,0 +1,847 @@
+using MySql.Data.MySqlClient;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Diagnostics;
+using static System.Diagnostics.Trace;
+using static System.String;
+
+namespace MultiDup
+{
+    /// <summary>
+    /// MySQL操作辅助类.
+    /// </summary>
+    public class MysqlUtil : IDisposable
+    {
+
+        /// <summary>
+        /// MySQL字段详细信息,(用于GetFiledInfo方法返回值中取信息,可以用nameof来去掉硬编码字符串以防止拼写错误)
+        /// </summary>
+        public enum EFieldInfo
+        {
+            /// <summary>
+            /// 字段类型 in .net
+            /// </summary>
+            Type,
+            /// <summary>
+            /// 是否可空
+            /// </summary>
+            Null,
+            /// <summary>
+            /// 索引类型
+            /// </summary>
+            Key,
+            /// <summary>
+            /// 默认值
+            /// </summary>
+            Default,
+            /// <summary>
+            /// 注释
+            /// </summary>
+            Comment,
+            /// <summary>
+            /// 字符集
+            /// </summary>
+            Collation,
+            /// <summary>
+            /// 额外(自增等)
+            /// </summary>
+            Extra,
+            /// <summary>
+            /// 权限
+            /// </summary>
+            Privileges
+        }
+
+        #region `    基础代码   `
+        static MysqlUtil mInst;
+        public static MysqlUtil Ins
+        {
+            get
+            {
+                if (null == mInst)
+                {
+                    mInst = new MysqlUtil();
+                }
+                return mInst;
+            }
+        }
+
+        private MysqlUtil()
+        {
+            conn = new MySqlConnection(Config.Ins.mysql.ConnectionString);
+            conn.Open();
+            Assert(conn.State == ConnectionState.Open, "连接到数据库失败");
+        }
+  
+        private MySqlConnection conn = null;
+        /// <summary>
+        /// 建立连接
+        /// </summary>
+        /// <param name="host"></param>
+        /// <param name="port"></param>
+        /// <param name="user"></param>
+        /// <param name="pwd"></param>
+        /// <param name="dbName"></param>
+        public void Connect(string host, string port, string user, string pwd, string dbName)
+        {
+            var connStr = string.Format(
+                              "server={0};port={1};user={2};password={3};database={4};Charset=utf8",
+                              host, port, user, pwd, dbName);
+            conn = new MySqlConnection(connStr);
+            conn.Open();
+            Assert(conn.State == ConnectionState.Open, "连接到数据库失败");
+        }
+        /// <summary>
+        /// 关闭连接
+        /// </summary>
+        public void Close()
+        {
+            conn.Close();
+        }
+        /// <summary>
+        /// 释放资源
+        /// </summary>
+        public void Dispose()
+        {
+            conn.Dispose();
+        }
+        #endregion
+
+        #region `    数据读    `
+
+        /// <summary>
+        /// 读取数据库表-数据操作部分逻辑需要参数中定义
+        /// 一般情况下,使用其余八个读取函数即可,如果需要自己定制数据结构,可以通过在此函数回调中自行遍历整个数据表.
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="working">action to MysqlDataReader</param>
+        public void ReadTabel(string tableName, Action<MySqlDataReader> working)
+        {
+            Assert(TableExist(tableName), "表 " + tableName + " 不存在!");
+            var sql = string.Format("SELECT * FROM {0};", tableName);
+            using (var cmd = new MySqlCommand(sql, conn))
+            using (var rdr = cmd.ExecuteReader())
+            {
+                if (null != working) working.Invoke(rdr);
+            }
+        }
+
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个JArray中, []
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// /// <param name="excludeFields">排除的字段名列表</param>
+        /// <returns>JArray</returns>
+        public JArray ReadTabelToJArray(string tableName, params string[] excludeFields)
+        {
+            var ex = new List<string>(excludeFields);
+            var arr = new JArray();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var jobj = new JObject();
+
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            jobj.Add(rdr.GetName(i), JToken.FromObject(rdr.GetValue(i)));
+                        }
+                    }
+                    arr.Add(jobj);
+                }
+            });
+            return arr;
+        }
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个JObject中, {}
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="indexFieldName">用做索引的字段名</param>
+        /// <param name="excludeFields">排除的字段名列表</param>
+        /// <returns>{}</returns>
+        public JObject ReadTabelToJObject(string tableName, string indexFieldName, out bool isArray, params string[] excludeFields)
+        {
+            Assert(HasFiledInTable(tableName, indexFieldName),
+                Format("在表[{0}]中未找到key:[{1}]", tableName, indexFieldName));
+            bool _isArray = false;
+            var ex = new List<string>(excludeFields);
+            //ex.Add(indexFieldName);
+            var obj = new JObject();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var jobj = new JObject();
+                    var id = "";
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (rdr.GetName(i) == indexFieldName)
+                        {
+                            id = rdr.GetValue(i).ToString();
+                        }
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            object v = rdr.GetValue(i);
+
+                            if (DBNull.Value == v)
+                            {
+                                var t = rdr.GetFieldType(i).Name;
+                                switch (t)
+                                {
+                                    case "Int32":
+                                        v = 0;
+                                        break;
+                                    case "String":
+                                        v = "";
+                                        break;
+                                    case "Single":
+                                        v = 0;
+                                        break;
+                                }
+                            }
+                            jobj.Add(rdr.GetName(i), JToken.FromObject(v));
+                        }
+                    }
+                    if (obj[id] == null)
+                    {  // 这段代码智能转换多值对应为[]
+                        obj.Add(id, jobj);
+                    }
+                    else
+                    {
+                        _isArray = true;
+                        var arr = obj[id] as JArray;
+                        if (null != arr)
+                        {
+                            arr.Add(jobj);
+                        }
+                        else
+                        {
+                            obj[id] = new JArray() { obj[id], jobj };
+                        }
+                    }
+                }
+
+            });
+            if (_isArray)
+            {
+                foreach (var o in obj.Properties())
+                {
+                    if (null == (o.Value as JArray))
+                    {
+                        obj[o.Name] = new JArray() { o.Value };
+                    }
+                }
+            }
+            isArray = _isArray;
+            return obj;
+        }
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个JObject中, {k->[],k->[],...}
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="indexFieldName">用做索引的字段名</param>
+        /// <param name="excludeFields">排除的字段名列表</param>
+        /// <returns>{}</returns>
+        public JObject ReadTabelToJObjectWithArrayValues(string tableName, string indexFieldName, params string[] excludeFields)
+        {
+            Assert(HasFiledInTable(tableName, indexFieldName),
+                Format("在表[{0}]中未找到key:[{1}]", tableName, indexFieldName));
+
+            var ex = new List<string>(excludeFields);
+            //ex.Add(indexFieldName);
+            var obj = new JObject();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var jobj = new JObject();
+                    var id = "";
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (rdr.GetName(i) == indexFieldName)
+                        {
+                            id = rdr.GetValue(i).ToString();
+                        }
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            object v = rdr.GetValue(i);
+
+                            if (DBNull.Value == v)
+                            {
+                                var t = rdr.GetFieldType(i).Name;
+                                switch (t)
+                                {
+                                    case "Int32":
+                                        v = 0;
+                                        break;
+                                    case "String":
+                                        v = "";
+                                        break;
+                                    case "Single":
+                                        v = 0;
+                                        break;
+                                }
+                            }
+                            jobj.Add(rdr.GetName(i), JToken.FromObject(v));
+                        }
+                    }
+
+                    var arr = obj[id] as JArray;
+                    if (null != arr)
+                    {
+                        arr.Add(jobj);
+                    }
+                    else
+                    {
+                        obj[id] = new JArray() { jobj };
+                    }
+
+                }
+
+            });
+
+            return obj;
+        }
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个简单的key=>value结构中. 即只取表中的两个字段
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="keyName">用作属性名称的字段</param>
+        /// <param name="valueName">用作值的字段</param>
+        /// <returns>{k:v,k:v,...}</returns>
+        public JObject ReadTabelToSimpleJObject(string tableName, string keyName, string valueName)
+        {
+            Assert(HasFiledInTable(tableName, keyName),
+                    Format("在表[{0}]中未找到key:[{1}]", tableName, keyName));
+            Assert(HasFiledInTable(tableName, valueName),
+                Format("在表[{0}]中未找到字段:[{1}]", tableName, valueName));
+            var jobj = new JObject();
+
+            ReadTabel(tableName, rdr =>
+           {
+               while (rdr.Read())
+               {
+                   var key = "";
+                   Object value = null;
+                   for (int i = 0; i < rdr.FieldCount; i++)
+                   {
+                       if (rdr.GetName(i) == keyName)
+                       {
+                           key = rdr.GetValue(i).ToString();
+                       }
+                       if (rdr.GetName(i) == valueName)
+                       {
+                           value = rdr.GetValue(i);
+                       }
+                   }
+                   jobj.Add(key, JToken.FromObject(value));
+               }
+           });
+
+            return jobj;
+        }
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个由两个字段内容拼接成的字符串作为索引的JObject中
+        /// </summary>
+        /// <param name="tableName">表名称</param>
+        /// <param name="firstFieldName">第一个字段</param>
+        /// <param name="secondFieldName">第二个字段</param>
+        /// <param name="excludeFields">排除的字段名...</param>
+        /// <returns> {field(1)-field(2):{},...} </returns>
+        public JObject ReadTabelToJObjectWithCombinedIndex(string tableName, string firstFieldName, string secondFieldName, out bool isArray, params string[] excludeFields)
+        {
+            Assert(HasFiledInTable(tableName, firstFieldName),
+                 Format("在表[{0}]中未找到字段:[{1}]", tableName, firstFieldName));
+            Assert(HasFiledInTable(tableName, secondFieldName),
+                Format("在表[{0}]中未找到字段:[{1}]", tableName, secondFieldName));
+            bool _isArray = false;
+            var ex = new List<string>(excludeFields);
+            //ex.Add(firstFieldName);
+            //ex.Add(secondFieldName);
+            var obj = new JObject();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var jobj = new JObject();
+                    var firstId = "";
+                    var secondId = "";
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (rdr.GetName(i) == firstFieldName)
+                        {
+                            firstId = rdr.GetValue(i).ToString();
+                        }
+                        if (rdr.GetName(i) == secondFieldName)
+                        {
+                            secondId = rdr.GetValue(i).ToString();
+                        }
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            Object v = rdr.GetValue(i);
+                            if (DBNull.Value == v)
+                            {
+                                var t = rdr.GetFieldType(i).Name;
+                                switch (t)
+                                {
+                                    case "Int32":
+                                        v = 0;
+                                        break;
+                                    case "String":
+                                        v = "";
+                                        break;
+                                    case "Single":
+                                        v = 0;
+                                        break;
+                                }
+                            }
+                            jobj.Add(rdr.GetName(i), JToken.FromObject(v));
+                        }
+                    }
+                    var key = firstId + "-" + secondId;
+                    if (obj[key] == null)
+                    {  // 防止重复
+                        obj.Add(key, jobj);
+                    }
+                    else
+                    {  // 已有对象 => 智能转换多值对应为[]
+                        _isArray = true;
+                        var arr = obj[key] as JArray;
+                        if (arr != null)
+                        { // 已经是jarray
+                            arr.Add(jobj);
+                        }
+                        else
+                        {    // 创建Jarray
+                            obj[key] = new JArray() { obj[key], jobj };
+                        }
+                    }
+                }
+            });
+
+            if (_isArray)
+            {
+                foreach (var o in obj.Properties())
+                {
+                    if (null == (o.Value as JArray))
+                    {
+                        obj[o.Name] = new JArray() { o.Value };
+                    }
+                }
+            }
+            isArray = _isArray;
+            return obj;
+        }
+
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个{key11:{key1_21:{},key1_22:{},...},key12:{key2_21:{},key2_22:{},...},...}结构数据中
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="indexFieldName">用做索引的字段名</param>
+        /// <param name="secondaryIndexName">用做次级索引的字段名</param>
+        /// <param name="excludeFields">排除的字段名列表</param>
+        /// <returns>拥有二级索引的json对象,二级节点是{}</returns>
+        public JObject ReadTabelToJObjectWithSecondaryIndex(string tableName,
+            string indexFieldName, string secondaryIndexName, out bool isArray, params string[] excludeFields)
+        {
+            Assert(HasFiledInTable(tableName, indexFieldName),
+            Format("在表[{0}]中未找到字段:[{1}]", tableName, indexFieldName));
+            Assert(HasFiledInTable(tableName, secondaryIndexName),
+                Format("在表[{0}]中未找到字段:[{1}]", tableName, secondaryIndexName));
+            var _isArray = false;
+            var ex = new List<string>(excludeFields);
+            //ex.Add(indexFieldName);
+            //ex.Add(secondaryIndexName);
+            var obj = new JObject();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var jobj = new JObject();
+                    var id = "";
+                    var secondId = "";
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (rdr.GetName(i) == indexFieldName)
+                        {
+                            id = rdr.GetValue(i).ToString();
+                        }
+                        if (rdr.GetName(i) == secondaryIndexName)
+                        {
+                            secondId = rdr.GetValue(i).ToString();
+                        }
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            Object v = rdr.GetValue(i);
+                            if (DBNull.Value == v)
+                            {
+                                var t = rdr.GetFieldType(i).Name;
+                                switch (t)
+                                {
+                                    case "Int32":
+                                        v = 0;
+                                        break;
+                                    case "String":
+                                        v = "";
+                                        break;
+                                    case "Single":
+                                        v = 0;
+                                        break;
+                                }
+                            }
+                            jobj.Add(rdr.GetName(i), JToken.FromObject(v));
+                        }
+                    }
+
+                    if (obj[id] == null)
+                    {  // 没有建立一级对象
+                        var o = new JObject();
+                        o.Add(secondId, jobj);
+                        obj.Add(id, o);
+                    }
+                    else
+                    {  // 已有一级对象
+                        var o = obj[id] as JObject;  // 一级对象
+                        if (o[secondId] == null)
+                        {  // 添加新值
+                            o.Add(secondId, jobj);
+                        }
+                        else
+                        {    // 已有对象 => 智能转换多值对应为[]
+                            _isArray = true;
+                            var arr = o[secondId] as JArray;
+                            if (arr != null)
+                            { // 已经是jarray
+                                arr.Add(jobj);
+                            }
+                            else
+                            {    // 创建Jarray
+                                o[secondId] = new JArray() { o[secondId], jobj };
+                            }
+                        }
+                    }
+                }
+            });
+            if (_isArray)
+            {
+                foreach (var o in obj.Properties())
+                {
+                    foreach (var o2 in (o.Value as JObject).Properties())
+                    {
+                        if (!(obj[o.Name][o2.Name] is JArray))
+                        {
+                            obj[o.Name][o2.Name] = new JArray() { obj[o.Name][o2.Name] };
+                        }
+                    }
+                }
+            }
+            isArray = _isArray;
+            return obj;
+        }
+
+
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个行数组[{},{}]
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="excludeFields">排除的字段名列表</param>
+        /// <returns>普通行数组[{},{},...]</returns>
+        public Dictionary<string, object>[] ReadTabelToArray(string tableName, params string[] excludeFields)
+        {
+            var ex = new List<string>(excludeFields);
+            var arr = new List<Dictionary<string, object>>();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var r = new Dictionary<string, object>(rdr.FieldCount - 1);
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            r.Add(rdr.GetName(i), rdr.GetValue(i));
+                        }
+                    }
+                    arr.Add(r);
+                }
+            });
+            return arr.ToArray();
+        }
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个dic中 {"key":{}}
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="indexFieldName">仅限索引字段(1对多的情况暂不支持)</param>
+        /// <param name="excludeFields">排除的字段名列表</param>
+        /// <typeparam name="T">string, number</typeparam>
+        /// <returns>嵌套的dictionary:{k:{k,v},k:{k:v},...}</returns>
+        public Dictionary<T, Dictionary<string, object>> ReadTabelToDic_Dic<T>(string tableName, string indexFieldName, params string[] excludeFields)
+        {
+            Assert(HasFiledInTable(tableName, indexFieldName),
+                Format("在表[{0}]中未找到字段:[{1}]", tableName, indexFieldName));
+            var ex = new List<string>(excludeFields);
+            //ex.Add(indexFieldName);
+            var dic = new Dictionary<T, Dictionary<string, object>>();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var r = new Dictionary<string, object>(rdr.FieldCount - 1);
+                    var t = default(T);
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (rdr.GetName(i) == indexFieldName)
+                        {
+                            t = (T)rdr.GetValue(i);
+                        }
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            r.Add(rdr.GetName(i), rdr.GetValue(i));
+                        }
+                    }
+                    dic.Add(t, r);
+                }
+
+            });
+            return dic;
+        }
+
+        /// <summary>
+        /// 读取数据库表 -> 到一个dic中 {"key":[]}
+        /// </summary>
+        /// <param name="tableName">表名</param>
+        /// <param name="indexFieldName">索引字段名</param>
+        /// <param name="excludeFields">排除的字段名列表</param>
+        /// <typeparam name="T">string, number</typeparam>
+        /// <returns>复杂嵌套dictionary:{k:[{},{},..],k:[{},{},..]..}</returns>
+        public Dictionary<T, List<Dictionary<string, object>>> ReadTabelToDic_List<T>(string tableName, string indexFieldName, params string[] excludeFields)
+        {
+            Assert(HasFiledInTable(tableName, indexFieldName),
+                Format("在表[{0}]中未找到字段:[{1}]", tableName, indexFieldName));
+            var ex = new List<string>(excludeFields);
+            //ex.Add(indexFieldName);
+            var dic = new Dictionary<T, List<Dictionary<string, object>>>();
+            ReadTabel(tableName, rdr =>
+            {
+                while (rdr.Read())
+                {
+                    var r = new Dictionary<string, object>(rdr.FieldCount - 1);
+                    var t = default(T);
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (rdr.GetName(i) == indexFieldName)
+                        {
+                            t = (T)rdr.GetValue(i);
+                        }
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            r.Add(rdr.GetName(i), rdr.GetValue(i));
+                        }
+                    }
+                    if (dic.ContainsKey(t))
+                    {
+                        dic[t].Add(r);
+                    }
+                    else
+                    {
+                        dic.Add(t, new List<Dictionary<string, object>> { r });
+                    }
+                }
+            });
+            return dic;
+        }
+
+        #endregion
+
+        #region `   数据写   `
+
+        /// <summary>
+        /// 执行非查询类的SQL语句,比如插入/更新/删除之类的.
+        /// </summary>
+        /// <param name="sql"></param>
+        /// <returns></returns>
+        public int ExecuteSqlNonQuery(string sql) {
+            using var cmd = new MySqlCommand(sql, conn);
+            return cmd.ExecuteNonQuery();
+        }
+
+
+
+        #endregion
+
+        #region '    辅助方法    '
+
+        /// <summary>
+        /// 查询某表中是否存在指定字段
+        /// </summary>
+        /// <param name="tableName"></param>
+        /// <param name="fieldName"></param>
+        /// <returns></returns>
+        public bool HasFiledInTable(string tableName, string fieldName)
+        {
+            Trace.Assert(TableExist(tableName), tableName + " 未找到");
+            return GetTableFieldsInfo(tableName).ContainsKey(fieldName);
+        }
+
+        /// <summary>
+        /// 检查指定的表格是否存在
+        /// </summary>
+        /// <param name="tableName"></param>
+        /// <returns></returns>
+        public bool TableExist(string tableName)
+        {
+            try
+            {
+                new MySqlCommand("select * from " + tableName + " limit 1;", conn).ExecuteNonQuery();
+            }
+            catch (MySqlException ex)
+            {
+                switch (ex.Number)
+                {
+                    case 1146:
+                        return false;
+                    default:
+                        Debug.WriteLine(ex.Message);
+                        return false;
+                }
+            }
+            return true;
+        }
+
+        /// <summary>
+        /// 执行sql
+        /// </summary>
+        /// <param name="sql"></param>
+        public void Query(string sql)
+        {
+            try
+            {
+                using (var cmd = new MySqlCommand(sql, conn))
+                {
+                    cmd.ExecuteNonQuery();
+                }
+            }
+            catch (MySqlException ex)
+            {
+                Debug.WriteLine(ex.Message);
+            }
+        }
+        /// <summary>
+        /// 执行sql 查询
+        /// </summary>
+        /// <param name="sqlcmd"></param>
+        /// <param name="working"></param>
+        public void QueryTable(string sqlcmd, Action<MySqlDataReader> working)
+        {
+            try
+            {
+                using (var cmd = new MySqlCommand(sqlcmd, conn))
+                using (MySqlDataReader rdr = cmd.ExecuteReader())
+                {
+
+                    if (null != working)
+                    {
+                        working.Invoke(rdr);
+                    }
+                }
+            }
+            catch (MySqlException ex)
+            {
+                Debug.WriteLine(ex.Message);
+            }
+        }
+
+        /// <summary>
+        /// 查询表格的字段信息  FieldName :
+        ///     <Type(字段类型 in .net)>
+        ///     <Collation(字符集及排序)>
+        ///     <Null(是否可空)>
+        ///     <Key(索引类型)>
+        ///     <Default(默认值)>
+        ///     <Extra(自增)>
+        ///     <Privileges(权限)>
+        ///     <Comment(注释)>
+        /// </summary>
+        /// <param name="tableName"></param>
+        /// <returns></returns>
+        public Dictionary<string, Dictionary<string, object>> GetTableFieldsInfo(string tableName)
+        {
+            var ex = new List<string>();
+            var dic = new Dictionary<string, Dictionary<string, object>>();
+            QueryTable(string.Format("show full fields from {0}", tableName), rdr =>
+            {
+                while (rdr.Read())
+                {
+
+                    var r = new Dictionary<string, object>();
+                    var fieldName = rdr.GetString(0);
+                    for (int i = 0; i < rdr.FieldCount; i++)
+                    {
+                        if (rdr.GetName(i) == "Field")
+                        {
+                            fieldName = rdr.GetString(i);
+                        }
+                        if (!ex.Contains(rdr.GetName(i)))
+                        {
+                            r.Add(rdr.GetName(i), rdr.GetValue(i));
+                        }
+
+                    }
+                    dic.Add(fieldName, r);
+                }
+            });
+            ReadTabel(tableName, rdr =>               // 用。net类型替换掉myslq 类型字符串
+            {
+                for (var i = 0; i < rdr.FieldCount; i++)
+                {
+
+                    var filedName = rdr.GetName(i);               // 字段名
+                    var filedType = rdr.GetFieldType(i).Name;     // 字段类型
+                    if (dic.ContainsKey(filedName))
+                    {
+                        dic[filedName][nameof(EFieldInfo.Type)] = filedType;
+                    }
+                }
+            });
+            return dic;
+        }
+
+        /// <summary>
+        /// 查询表格的最后修改时间
+        /// </summary>
+        /// <param name="tableName"></param>
+        /// <returns>最后修改时间或者创建时间, 异常: 返回当前时间</returns>
+        public DateTime TableLastModified(string tableName)
+        {
+            var sql = string.Format("SELECT UPDATE_TIME, CREATE_TIME " +
+                      "FROM information_schema.tables " +
+                      "where TABLE_SCHEMA='{0}' and TABLE_NAME='{1}'",
+                          this.conn.Database, tableName);
+            using (var cmd = new MySqlCommand(sql, conn))
+            using (MySqlDataReader rdr = cmd.ExecuteReader())
+            {
+                if (rdr.Read())
+                {  // 上次更新时间为null的情况下使用表的创建时间,都找不到值的情况下返回当前时间   
+                    return rdr.IsDBNull(0) ? (rdr.IsDBNull(1) ? DateTime.Now : rdr.GetDateTime(1)) : rdr.GetDateTime(0);
+                }
+            }
+            throw new Exception("没有找到对应的表");
+        }
+        #endregion
+
+
+
+    }
+}

+ 31 - 0
CSserver/MultiDup/db/Redis.cs

@@ -0,0 +1,31 @@
+using StackExchange.Redis;
+
+namespace MultiDup
+{
+  public  class Redis
+    {
+        private static ConnectionMultiplexer redis;
+
+        static public ConnectionMultiplexer Ins
+        {
+            get
+            {
+                if (null == redis)
+                {
+                    redis = ConnectionMultiplexer.Connect(Config.Ins.redis);
+                }
+                return redis;
+            }
+        }
+        /// <summary>
+        /// 获取操作redis数据库的接口对象
+        /// </summary>
+        /// <param name="id">指定db的编号</param>
+        /// <returns></returns>
+        public static IDatabase Rdb(int id = 0)
+        {
+            return Ins.GetDatabase(id);
+        }
+
+    }
+}

+ 138 - 0
CSserver/MultiDup/server/Lobby.cs

@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Threading.Channels;
+using ProtoDataBuff;
+
+using CSharpUtil;
+
+namespace MultiDup
+{
+
+
+    /// <summary>
+    /// 大厅 首先玩家连上来之后进入大厅, 下发当前房间列表, 可以创建/加入房间, 进入房间后归房间管
+    /// </summary>
+    internal class Lobby : Singleton<Lobby>
+    {
+
+        /// <summary>
+        /// 客户端
+        /// </summary>
+        public Dictionary<int, Peer> ClientPeers = new Dictionary<int, Peer>();
+        /// <summary>
+        /// 消息分发
+        /// </summary>
+        private Dictionary<eProtocalCommand, Action<int, sSocketData>> callbacks = new Dictionary<eProtocalCommand, Action<int, sSocketData>>();
+
+        /// <summary>
+        /// 客户端消息队列
+        /// </summary>
+        public Channel<KeyValuePair<int, sSocketData>> MsgChannel = Channel.CreateBounded<KeyValuePair<int, sSocketData>>(1000);
+
+
+        Dictionary<int,Room> roomDic = new Dictionary<int,Room>();
+
+
+        public Lobby()
+        {
+
+            this.callbacks.Add(eProtocalCommand.CsLeaveRoom, On_Leave);
+            callbacks.Add(eProtocalCommand.CsMdCreateRoom, On_CreateRoom);
+            callbacks.Add(eProtocalCommand.CsMdEnterRoom, On_EnterRoom);
+            var t = Task.Run(MsgLoop);
+
+        }
+        private object lock_peers = new object();
+        public void AddPeer(Peer p)
+        {
+            lock (lock_peers)
+            {
+                this.ClientPeers.Add(p.Id, p);
+            }
+
+        }
+        public void RemovePeer(int peerId)
+        {
+            lock (lock_peers)
+            {
+                if (this.ClientPeers.ContainsKey(peerId))
+                {
+                    this.ClientPeers.Remove(peerId);
+                }
+            }
+        }
+
+        public void OnNewPeerConnected(Peer peer)
+        {
+            AddPeer(peer);
+
+            peer.SendEvent(eProtocalCommand.ScMdGetRoomList, new SC_MD_GetRoomList() { });
+
+        }
+        /// <summary>
+        /// 创建一个房间(副本)
+        /// </summary>
+        /// <param name="data"></param>
+        void On_CreateRoom(int peerId, sSocketData data)
+        {
+            if (this.ClientPeers.TryGetValue(peerId, out var peer))
+            {
+                peer.SendEvent(eProtocalCommand.ScMdCreateRoom, new SC_MD_CreateRoom() { });
+                var room = new Room();
+                room.AddPeer(peer);                   // 转移进房间
+                roomDic.Add(room.Id,room);
+
+                //RemovePeer(peerId);                                                  // 从大厅移除
+            }
+        }
+
+        /// <summary>
+        /// 进入房间
+        /// </summary>
+        /// <param name="data"></param>
+        void On_EnterRoom(int peerId, sSocketData data)
+        {
+            var msg = CS_MD_EnterRoom.Parser.ParseFrom(data._data);
+            if (this.ClientPeers.TryGetValue(peerId, out var peer))
+            {
+                if (roomDic.TryGetValue(msg.RoomId, out var room)){
+                    room.AddPeer(peer);                
+                }
+
+            }
+        }
+
+        /// <summary>
+        /// 离开房间(战斗结束)
+        /// </summary>
+        /// <param name="data"></param>
+        void On_Leave(int peerId, sSocketData data)
+        {
+            if (this.ClientPeers.TryGetValue(peerId, out var peer))
+            {
+                peer.SendEvent(eProtocalCommand.ScMdLeaveRoom, new SC_MD_LeaveRoom() { });
+                peer.Close();
+            }
+        }
+
+        async void MsgLoop()
+        {
+            while (true)
+            {
+                var msg = await MsgChannel.Reader.ReadAsync();
+                if (callbacks.ContainsKey(msg.Value._protocallType))
+                {
+                    callbacks[msg.Value._protocallType](msg.Key, msg.Value);
+                }
+                else
+                {
+                    // 未找到消息处理逻辑
+                }
+            }
+        }
+    }
+}

+ 212 - 0
CSserver/MultiDup/server/Peer.cs

@@ -0,0 +1,212 @@
+using StackExchange.Redis;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net.Sockets;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using ProtoDataBuff;
+using pb = global::Google.Protobuf;
+namespace MultiDup
+{
+    /// <summary>
+    /// 客户端状态
+    /// </summary>
+    enum ClientState
+    {
+        /// <summary>
+        /// 已连接,未登录
+        /// </summary>
+        Connected,
+        /// <summary>
+        /// 已登录
+        /// </summary>
+        InRoom,
+        /// <summary>
+        /// 已开始游戏
+        /// </summary>
+        InGame,
+        /// <summary>
+        /// 已断开
+        /// </summary>
+        Leaved
+    }
+
+
+    /// <summary>
+    /// 一个连接对象的抽象,代表一个远端的对象
+    /// </summary>
+    class Peer
+    {
+        /// <summary>
+        /// 玩家属性字段
+        /// </summary>
+        public Dictionary<string, object> Properties = new();
+
+        /// <summary>
+        /// 客户端状态字段
+        /// </summary>
+        public ClientState CurrentState = ClientState.Connected;
+
+
+        /// <summary>
+        /// peer的唯一Id
+        /// </summary>
+        public readonly int Id;
+
+        /// <summary>
+        /// socket连接
+        /// </summary>
+        public readonly Socket Sock;
+
+        public string UID => Properties.TryGetValue(PropertyName.Uid, out var uid)? uid.ToString():"";
+            
+        public int zoneid => Properties.TryGetValue(PropertyName.Zoneid, out var zoneid)?Convert.ToInt32(zoneid):1;
+
+
+        public Room room;
+
+        /// <summary>
+        /// 线程安全的peer计数器
+        /// </summary>
+        private volatile static int _UniqPeerId = 0;
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="sock"></param>
+        public Peer(Socket sock)
+        {
+            this.Sock = sock;
+            Sock.ReceiveTimeout = 1500;                                         // 接收等待超时时间设为1.5秒 
+            Id = _UniqPeerId++;
+            var t = Task.Run(() => recv(Sock));                                 // 新建接收线程 
+            var tcs = Task.Run(WriteToClient);                                  // 向客户端发送消息线程
+        }
+
+
+        public void Close()
+        {
+            this.CurrentState = ClientState.Leaved;                              // 关闭接收窗口
+            //room.RemovePeer(this);                                      // 从房间中移除自己
+            
+            this.sendDataBuffer.Reader.Completion
+                .ContinueWith(t =>
+                Task.Delay(5000).ContinueWith(t1 => Sock.Close())
+                );                                                               // 延迟5秒关闭套接字
+        }
+
+        #region 消息收发
+
+        /// <summary>
+        /// 向客户端发送事件
+        /// </summary>
+        public void SendEvent(eProtocalCommand msgType, pb::IMessage msg)
+        {
+            SendToClient(msgType, msg);
+        }
+
+        /// <summary>
+        /// 向客户端发送消息
+        /// </summary>
+        /// <param name="msgType"></param>
+        /// <param name="msg"></param>
+        void SendToClient(eProtocalCommand msgType, pb::IMessage msg)
+        {
+            using var ms = new MemoryStream();
+            using var os = new pb::CodedOutputStream(ms);
+            msg.WriteTo(os);
+            os.Flush();
+            ms.Seek(0, SeekOrigin.Begin);
+            var sdata = sSocketData.FromBytes(msgType, ms.ToArray());
+            sendDataBuffer.Writer.WriteAsync(sdata).AsTask().Wait();
+        }
+
+
+        /// <summary>
+        /// 向客户端写入消息
+        /// </summary>
+        async void WriteToClient()
+        {
+            while (true)
+            {
+                try
+                {
+                    var msg = await sendDataBuffer.Reader.ReadAsync();
+                    await Sock.SendAsync(new ArraySegment<byte>(msg.ToBytes()), SocketFlags.None);
+                }
+                catch (Exception)
+                {
+                    break;
+                }
+
+            }
+            Close();
+        }
+
+
+        /// <summary>
+        /// 发送buffer
+        /// </summary>
+        private Channel<sSocketData> sendDataBuffer = Channel.CreateUnbounded<sSocketData>();
+
+        /// <summary>
+        /// 接收客户端发来的信息,客户端套接字对象
+        /// </summary>
+        /// <param name="socketclientpara"></param>    
+        async void recv(Socket socketServer)
+        {
+
+            var _databuffer = new DataBuffer();
+            byte[] arrServerRecMsg = new byte[4096];                                       // 创建一个内存缓冲区,其大小为4k字节  
+            while (true)
+            {
+                try
+                {
+                    var length = await socketServer.ReceiveAsync(new ArraySegment<byte>(arrServerRecMsg), SocketFlags.None);   // 将接收到的信息存入到内存缓冲区,并返回其字节数组的长度    
+                    if (length <= 0 || CurrentState == ClientState.Leaved)                                                     // 视为客户端已经close连接
+                    {
+                        break;
+                    }
+                    _databuffer.AddBuffer(arrServerRecMsg, length);                                  //将收到的数据添加到缓存器中
+                    while (_databuffer.GetData(out sSocketData _socketData))                         //取出一条完整数据,转到room的消息队列,由room统一进行逻辑处理 
+                    {
+                        Console.WriteLine(" recv: " + _socketData._protocallType);
+                        switch (CurrentState)
+                        {
+                            case ClientState.Connected:
+                                await Lobby.Instance.MsgChannel.Writer.WriteAsync(new KeyValuePair<int, sSocketData>(this.Id, _socketData)); // 放入大厅处理队列
+                                break;
+                            case ClientState.InRoom:
+                                if (null != room)
+                                {
+                                    await room.MsgChannel.Writer.WriteAsync(new KeyValuePair<int, sSocketData>(this.Id, _socketData));           // 放入房间channel
+                                }
+                                break;
+                            case ClientState.Leaved:
+                                break;
+                        }
+
+                    }
+                    Array.Clear(arrServerRecMsg, 0, length);
+                }
+                catch (SocketException e)
+                {
+                    if (e.ErrorCode == 10060)                                          // 超时的时候错误号码是10060
+                    {
+                        continue;                                                      // 继续等待  
+                    }
+                    break;
+                }
+                catch (Exception)
+                {
+                    break;
+                }
+            }
+
+            Close();                                                                 // 关闭
+        }
+        #endregion
+    }
+
+}

+ 228 - 0
CSserver/MultiDup/server/Room.cs

@@ -0,0 +1,228 @@
+using CSharpUtil;
+using CSharpUtil.Net;
+using ProtoDataBuff;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+using static System.Console;
+using pb = global::Google.Protobuf;
+
+namespace MultiDup
+{
+    /// <summary>
+    /// 房间状态
+    /// </summary>
+    enum RoomState
+    {
+        /// <summary>
+        /// 开启状态(可以连接登入)
+        /// </summary>
+        Open,
+        /// <summary>
+        /// 已关闭(不可继续登录)
+        /// </summary>
+        Close,
+    }
+
+    enum TargetType
+    {
+        /// <summary>
+        /// 所有
+        /// </summary>
+        All,
+        /// <summary>
+        /// 其他人
+        /// </summary>
+        Others,
+
+
+    }
+
+    class PropertyName
+    {
+        public const string Uid = nameof(Uid);
+        public const string Name = nameof(Name);
+        public const string Zoneid = nameof(Zoneid);
+        public const string Hp = nameof(Hp);
+        public const string MaxHp = nameof(MaxHp);
+        public const string TotalDamage = nameof(TotalDamage);
+    }
+
+    /// <summary>
+    /// 房间对象
+    /// </summary>
+    class Room
+    {
+
+        /// <summary>
+        /// 房间状态字段
+        /// </summary>
+        public RoomState CurrentState = RoomState.Close;
+
+        /// <summary>
+        /// 客户端
+        /// </summary>
+        public Dictionary<int, Peer> ClientPeers = new Dictionary<int, Peer>();
+
+        /// <summary>
+        /// 消息分发
+        /// </summary>
+        private Dictionary<eProtocalCommand, Action<int, sSocketData>> callbacks = new Dictionary<eProtocalCommand, Action<int, sSocketData>>();
+
+        /// <summary>
+        /// 客户端消息队列
+        /// </summary>
+        public Channel<KeyValuePair<int, sSocketData>> MsgChannel = Channel.CreateBounded<KeyValuePair<int, sSocketData>>(1000);
+
+        // 房间计数器
+        private static int _roomIdCounter = 0;
+        /// <summary>
+        /// 房间编号
+        /// </summary>
+        public int Id { get; }
+        public string Name => DateTime.Now.ToString("yyyyMMddHH");
+
+        /// <summary>
+        /// 构造函数
+        /// </summary> 
+        public Room()
+        {
+            Id = _roomIdCounter++;
+            //this.Name = DateTime.Now.ToString("yyyyMMddHH");
+
+            callbacks.Add(eProtocalCommand.CsMdBeginDup, On_Start);
+
+            callbacks.Add(eProtocalCommand.CsLeaveRoom, On_Leave);
+
+            var t = Task.Run(MsgLoop);
+            Open();
+        }
+
+        /// <summary>
+        /// 开始游戏 
+        /// </summary>
+        /// <param name="data"></param>
+        void On_Start(int peerId, sSocketData data)
+        {
+            if (this.ClientPeers.TryGetValue(peerId, out var peer))
+            {
+                Broadcast(TargetType.All, eProtocalCommand.ScMdBeginDup, new SC_MD_BeginDup() { });
+
+            }
+        }
+
+
+        /// <summary>
+        /// 离开房间(战斗结束)
+        /// </summary>
+        /// <param name="data"></param>
+        void On_Leave(int peerId, sSocketData data)
+        {
+            if (this.ClientPeers.TryGetValue(peerId, out var peer))
+            {
+                Broadcast(TargetType.All, eProtocalCommand.ScMdLeaveRoom, new SC_MD_LeaveRoom() { Uid = peer.UID, Zoneid = peer.zoneid });
+                peer.Close();
+                RemovePeer(peerId);
+            }
+        }
+
+
+        #region 网络
+        async void MsgLoop()
+        {
+            while (true)
+            {
+                var msg = await MsgChannel.Reader.ReadAsync();
+                if (callbacks.ContainsKey(msg.Value._protocallType))
+                {
+                    callbacks[msg.Value._protocallType](msg.Key, msg.Value);
+                }
+                else
+                {
+                    // 未找到消息处理逻辑
+                }
+            }
+        }
+        /// <summary>
+        /// 广播消息
+        /// </summary>
+        /// <param name="targetType"></param>
+        /// <param name="msgType"></param>
+        /// <param name="msg"></param>
+        private void Broadcast(TargetType targetType, eProtocalCommand msgType, pb::IMessage msg)
+        {
+            switch (targetType)
+            {
+                case TargetType.All:
+                    this.ClientPeers.Values.ToList().ForEach(p => p.SendEvent(msgType, msg));
+                    break;
+                case TargetType.Others:
+                    break;
+            }
+
+        }
+        #endregion
+
+        /// <summary>
+        /// 开启
+        /// </summary>
+        public void Open()
+        {
+
+            this.CurrentState = RoomState.Open;
+        }
+        public void Close()
+        {
+            this.CurrentState = RoomState.Close;
+            Task.Delay(TimeSpan.FromMinutes(1)).ContinueWith(t => Open());
+        }
+
+
+        private object lock_peers = new object();
+        public void AddPeer(Peer p)
+        {
+            if (CurrentState == RoomState.Open)
+            {
+                lock (lock_peers)
+                {
+                    p.CurrentState = ClientState.InRoom;
+                    p.room = this;
+                    this.ClientPeers.Add(p.Id, p);
+                }
+                Broadcast(TargetType.All, eProtocalCommand.ScMdEnterRoom, new SC_MD_EnterRoom() { Uid = p.UID, Zoneid = p.zoneid });
+            }
+            else
+            {
+
+                WriteLine("游戏结束2 // 这里不应该执行到!!!");
+                p.SendEvent(eProtocalCommand.ScGameOver, new SC_MD_EnterRoom() { });
+                p.Close();
+            }
+        }
+
+        public void RemovePeer(Peer p)
+        {
+            lock (lock_peers)
+            {
+                if (this.ClientPeers.ContainsKey(p.Id))
+                {
+                    this.ClientPeers.Remove(p.Id);
+                }
+            }
+        }
+        public void RemovePeer(int peerId)
+        {
+            lock (lock_peers)
+            {
+                if (this.ClientPeers.ContainsKey(peerId))
+                {
+                    this.ClientPeers.Remove(peerId);
+                }
+            }
+        }
+
+    }
+
+}

+ 37 - 0
CSserver/PBReferens/pb/MsgTypeEnum.proto

@@ -5,6 +5,11 @@ syntax = "proto3";
 enum eProtocalCommand {
   // 无操作
   noop = 0;
+
+  //
+  //=======  C => S  =======
+  //
+
   // 登录()
   CS_Login = 1;  
   // 上报伤害输出
@@ -13,11 +18,31 @@ enum eProtocalCommand {
   CS_LeaveRoom =3;
   // 上报玩家剩余血量
   CS_ReportUserHp=4;
+
   // 聊天, 客户端初始化
   CS_ChatLogin = 10;
   // 聊天, 发送消息
   CS_ChatSendMsg = 11;
 
+  // ----多人副本----
+
+  // 多人副本, 创建房间
+  CS_MD_CreateRoom =21;
+  // 多人副本, 申请房间(列表)
+  CS_MD_GetRoomList = 22;
+  // 多人副本, 加入房间
+  CS_MD_EnterRoom = 23;
+  // 多人副本, 离开房间
+  CS_MD_LeaveRoom =24;
+  // 多人副本, 开始游戏(创建者)
+  CS_MD_BeginDup = 25;
+
+
+  //
+  //=======  S => C  =======
+  //
+
+
   // 登录结果
   SC_Login =100;
   // 更新数据
@@ -30,4 +55,16 @@ enum eProtocalCommand {
   // 聊天, 新消息
   SC_ChatNewMsg = 111;
 
+  // ---- 多人副本 ----
+  // 多人副本, 创建房间
+  SC_MD_CreateRoom =121;
+  // 多人副本, 申请房间(列表)
+  SC_MD_GetRoomList = 122;
+  // 多人副本, 加入房间
+  SC_MD_EnterRoom = 123;
+  // 多人副本, 离开房间
+  SC_MD_LeaveRoom =124;
+  // 多人副本, 开始游戏(创建者)
+  SC_MD_BeginDup = 125;
+
 }

+ 74 - 0
CSserver/PBReferens/pb/MultiDup.proto

@@ -0,0 +1,74 @@
+syntax = "proto3";
+// 多人副本
+package MultiDup;
+ 
+//
+  // 多人副本, 创建房间
+ message CS_MD_CreateRoom {
+	 int32 Zoneid=1;    // zoneid
+	 string Uid=2;      // 玩家id
+	 int32 Mapid=3;     // 地图id
+ }
+  // 多人副本, 申请房间(列表)
+ message CS_MD_GetRoomList {
+	 int32 Zoneid=1;
+	 string Uid =2;
+	 
+ }
+  // 多人副本, 加入房间
+ message CS_MD_EnterRoom {
+     int32 Zoneid=1;
+	 string Uid =2;
+	 int32 RoomId=3;    // 房间id
+ }
+  // 多人副本, 离开房间
+ message CS_MD_LeaveRoom {
+ 	 int32 Zoneid=1;
+	 string Uid =2;
+	 //int32 RoomId=3;    // 房间id (理论上已经处于房间中不需要此字段)
+ }
+  // 多人副本, 开始游戏(创建者)
+ message CS_MD_BeginDup {
+ 	 int32 Zoneid=1;
+	 string Uid =2;
+	 //int32 RoomId=3;    // 房间id (理论上已经处于房间中不需要此字段)
+ }
+
+
+ // 
+  // 多人副本, 创建房间
+ message SC_MD_CreateRoom {
+	 int32 Zoneid=1;
+	 string Uid =2;
+	 int32 Mapid=3;
+     int32 RoomId=4;    // 房间id (服务端分配)
+     
+ }
+  // 多人副本, 申请房间(列表)
+ message SC_MD_GetRoomList {
+	message RoomInfo{
+		int32 RoomId=1;
+		int32 Mapid=2;
+		repeated string  PlayerUids=3;  //??
+		string battleserver =4;
+	}	 
+	repeated RoomInfo RoomInfos=1;
+
+ }
+  // 多人副本, 加入房间
+ message SC_MD_EnterRoom {
+     int32 Zoneid=1;
+	 string Uid =2;       // (新进id)
+	 int32 Mapid=3;
+	 string Ip=4;         // 战斗服务器ip
+	 int32 Port=5;        // 战斗服务器端口
+ }
+  // 多人副本, 离开房间
+ message SC_MD_LeaveRoom {
+     int32 Zoneid=1;
+	 string Uid =2;       // (离开id)
+ }
+  // 多人副本, 开始游戏(创建者)
+ message SC_MD_BeginDup {
+      
+ }

+ 50 - 4
CSserver/PBReferens/pbcs/MsgTypeEnum.cs

@@ -20,12 +20,17 @@ public static partial class MsgTypeEnumReflection {
   static MsgTypeEnumReflection() {
     byte[] descriptorData = global::System.Convert.FromBase64String(
         string.Concat(
-          "ChRwYi9Nc2dUeXBlRW51bS5wcm90byrpAQoQZVByb3RvY2FsQ29tbWFuZBII",
+          "ChRwYi9Nc2dUeXBlRW51bS5wcm90byq/AwoQZVByb3RvY2FsQ29tbWFuZBII",
           "CgRub29wEAASDAoIQ1NfTG9naW4QARITCg9DU19SZXBvcnREYW1hZ2UQAhIQ",
           "CgxDU19MZWF2ZVJvb20QAxITCg9DU19SZXBvcnRVc2VySHAQBBIQCgxDU19D",
-          "aGF0TG9naW4QChISCg5DU19DaGF0U2VuZE1zZxALEgwKCFNDX0xvZ2luEGQS",
-          "FwoTU0NfVXBkYXRlUHJvcGVydGllcxBlEg8KC1NDX0dhbWVPdmVyEGYSEAoM",
-          "U0NfQ2hhdExvZ2luEG4SEQoNU0NfQ2hhdE5ld01zZxBvYgZwcm90bzM="));
+          "aGF0TG9naW4QChISCg5DU19DaGF0U2VuZE1zZxALEhQKEENTX01EX0NyZWF0",
+          "ZVJvb20QFRIVChFDU19NRF9HZXRSb29tTGlzdBAWEhMKD0NTX01EX0VudGVy",
+          "Um9vbRAXEhMKD0NTX01EX0xlYXZlUm9vbRAYEhIKDkNTX01EX0JlZ2luRHVw",
+          "EBkSDAoIU0NfTG9naW4QZBIXChNTQ19VcGRhdGVQcm9wZXJ0aWVzEGUSDwoL",
+          "U0NfR2FtZU92ZXIQZhIQCgxTQ19DaGF0TG9naW4QbhIRCg1TQ19DaGF0TmV3",
+          "TXNnEG8SFAoQU0NfTURfQ3JlYXRlUm9vbRB5EhUKEVNDX01EX0dldFJvb21M",
+          "aXN0EHoSEwoPU0NfTURfRW50ZXJSb29tEHsSEwoPU0NfTURfTGVhdmVSb29t",
+          "EHwSEgoOU0NfTURfQmVnaW5EdXAQfWIGcHJvdG8z"));
     descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
         new pbr::FileDescriptor[] { },
         new pbr::GeneratedClrTypeInfo(new[] {typeof(global::eProtocalCommand), }, null));
@@ -64,6 +69,26 @@ public enum eProtocalCommand {
   /// </summary>
   [pbr::OriginalName("CS_ChatSendMsg")] CsChatSendMsg = 11,
   /// <summary>
+  /// 多人副本, 创建房间
+  /// </summary>
+  [pbr::OriginalName("CS_MD_CreateRoom")] CsMdCreateRoom = 21,
+  /// <summary>
+  /// 多人副本, 申请房间(列表)
+  /// </summary>
+  [pbr::OriginalName("CS_MD_GetRoomList")] CsMdGetRoomList = 22,
+  /// <summary>
+  /// 多人副本, 加入房间
+  /// </summary>
+  [pbr::OriginalName("CS_MD_EnterRoom")] CsMdEnterRoom = 23,
+  /// <summary>
+  /// 多人副本, 离开房间
+  /// </summary>
+  [pbr::OriginalName("CS_MD_LeaveRoom")] CsMdLeaveRoom = 24,
+  /// <summary>
+  /// 多人副本, 开始游戏(创建者)
+  /// </summary>
+  [pbr::OriginalName("CS_MD_BeginDup")] CsMdBeginDup = 25,
+  /// <summary>
   /// 登录结果
   /// </summary>
   [pbr::OriginalName("SC_Login")] ScLogin = 100,
@@ -83,6 +108,27 @@ public enum eProtocalCommand {
   /// 聊天, 新消息
   /// </summary>
   [pbr::OriginalName("SC_ChatNewMsg")] ScChatNewMsg = 111,
+  /// <summary>
+  /// ---- 多人副本 ----
+  /// 多人副本, 创建房间
+  /// </summary>
+  [pbr::OriginalName("SC_MD_CreateRoom")] ScMdCreateRoom = 121,
+  /// <summary>
+  /// 多人副本, 申请房间(列表)
+  /// </summary>
+  [pbr::OriginalName("SC_MD_GetRoomList")] ScMdGetRoomList = 122,
+  /// <summary>
+  /// 多人副本, 加入房间
+  /// </summary>
+  [pbr::OriginalName("SC_MD_EnterRoom")] ScMdEnterRoom = 123,
+  /// <summary>
+  /// 多人副本, 离开房间
+  /// </summary>
+  [pbr::OriginalName("SC_MD_LeaveRoom")] ScMdLeaveRoom = 124,
+  /// <summary>
+  /// 多人副本, 开始游戏(创建者)
+  /// </summary>
+  [pbr::OriginalName("SC_MD_BeginDup")] ScMdBeginDup = 125,
 }
 
 #endregion

+ 1886 - 0
CSserver/PBReferens/pbcs/MultiDup.cs

@@ -0,0 +1,1886 @@
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: pb/MultiDup.proto
+#pragma warning disable 1591, 0612, 3021
+#region Designer generated code
+
+using pb = global::Google.Protobuf;
+using pbc = global::Google.Protobuf.Collections;
+using pbr = global::Google.Protobuf.Reflection;
+using scg = global::System.Collections.Generic;
+namespace MultiDup {
+
+  /// <summary>Holder for reflection information generated from pb/MultiDup.proto</summary>
+  public static partial class MultiDupReflection {
+
+    #region Descriptor
+    /// <summary>File descriptor for pb/MultiDup.proto</summary>
+    public static pbr::FileDescriptor Descriptor {
+      get { return descriptor; }
+    }
+    private static pbr::FileDescriptor descriptor;
+
+    static MultiDupReflection() {
+      byte[] descriptorData = global::System.Convert.FromBase64String(
+          string.Concat(
+            "ChFwYi9NdWx0aUR1cC5wcm90bxIITXVsdGlEdXAiPgoQQ1NfTURfQ3JlYXRl",
+            "Um9vbRIOCgZab25laWQYASABKAUSCwoDVWlkGAIgASgJEg0KBU1hcGlkGAMg",
+            "ASgFIjAKEUNTX01EX0dldFJvb21MaXN0Eg4KBlpvbmVpZBgBIAEoBRILCgNV",
+            "aWQYAiABKAkiPgoPQ1NfTURfRW50ZXJSb29tEg4KBlpvbmVpZBgBIAEoBRIL",
+            "CgNVaWQYAiABKAkSDgoGUm9vbUlkGAMgASgFIi4KD0NTX01EX0xlYXZlUm9v",
+            "bRIOCgZab25laWQYASABKAUSCwoDVWlkGAIgASgJIi0KDkNTX01EX0JlZ2lu",
+            "RHVwEg4KBlpvbmVpZBgBIAEoBRILCgNVaWQYAiABKAkiTgoQU0NfTURfQ3Jl",
+            "YXRlUm9vbRIOCgZab25laWQYASABKAUSCwoDVWlkGAIgASgJEg0KBU1hcGlk",
+            "GAMgASgFEg4KBlJvb21JZBgEIAEoBSKhAQoRU0NfTURfR2V0Um9vbUxpc3QS",
+            "NwoJUm9vbUluZm9zGAEgAygLMiQuTXVsdGlEdXAuU0NfTURfR2V0Um9vbUxp",
+            "c3QuUm9vbUluZm8aUwoIUm9vbUluZm8SDgoGUm9vbUlkGAEgASgFEg0KBU1h",
+            "cGlkGAIgASgFEhIKClBsYXllclVpZHMYAyADKAkSFAoMYmF0dGxlc2VydmVy",
+            "GAQgASgJIlcKD1NDX01EX0VudGVyUm9vbRIOCgZab25laWQYASABKAUSCwoD",
+            "VWlkGAIgASgJEg0KBU1hcGlkGAMgASgFEgoKAklwGAQgASgJEgwKBFBvcnQY",
+            "BSABKAUiLgoPU0NfTURfTGVhdmVSb29tEg4KBlpvbmVpZBgBIAEoBRILCgNV",
+            "aWQYAiABKAkiEAoOU0NfTURfQmVnaW5EdXBiBnByb3RvMw=="));
+      descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
+          new pbr::FileDescriptor[] { },
+          new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.CS_MD_CreateRoom), global::MultiDup.CS_MD_CreateRoom.Parser, new[]{ "Zoneid", "Uid", "Mapid" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.CS_MD_GetRoomList), global::MultiDup.CS_MD_GetRoomList.Parser, new[]{ "Zoneid", "Uid" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.CS_MD_EnterRoom), global::MultiDup.CS_MD_EnterRoom.Parser, new[]{ "Zoneid", "Uid", "RoomId" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.CS_MD_LeaveRoom), global::MultiDup.CS_MD_LeaveRoom.Parser, new[]{ "Zoneid", "Uid" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.CS_MD_BeginDup), global::MultiDup.CS_MD_BeginDup.Parser, new[]{ "Zoneid", "Uid" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.SC_MD_CreateRoom), global::MultiDup.SC_MD_CreateRoom.Parser, new[]{ "Zoneid", "Uid", "Mapid", "RoomId" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.SC_MD_GetRoomList), global::MultiDup.SC_MD_GetRoomList.Parser, new[]{ "RoomInfos" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.SC_MD_GetRoomList.Types.RoomInfo), global::MultiDup.SC_MD_GetRoomList.Types.RoomInfo.Parser, new[]{ "RoomId", "Mapid", "PlayerUids", "Battleserver" }, null, null, null)}),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.SC_MD_EnterRoom), global::MultiDup.SC_MD_EnterRoom.Parser, new[]{ "Zoneid", "Uid", "Mapid", "Ip", "Port" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.SC_MD_LeaveRoom), global::MultiDup.SC_MD_LeaveRoom.Parser, new[]{ "Zoneid", "Uid" }, null, null, null),
+            new pbr::GeneratedClrTypeInfo(typeof(global::MultiDup.SC_MD_BeginDup), global::MultiDup.SC_MD_BeginDup.Parser, null, null, null, null)
+          }));
+    }
+    #endregion
+
+  }
+  #region Messages
+  /// <summary>
+  ///
+  /// 多人副本, 创建房间
+  /// </summary>
+  public sealed partial class CS_MD_CreateRoom : pb::IMessage<CS_MD_CreateRoom> {
+    private static readonly pb::MessageParser<CS_MD_CreateRoom> _parser = new pb::MessageParser<CS_MD_CreateRoom>(() => new CS_MD_CreateRoom());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CS_MD_CreateRoom> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[0]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_CreateRoom() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_CreateRoom(CS_MD_CreateRoom other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+      mapid_ = other.mapid_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_CreateRoom Clone() {
+      return new CS_MD_CreateRoom(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    /// <summary>
+    /// zoneid
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    /// <summary>
+    /// 玩家id
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "Mapid" field.</summary>
+    public const int MapidFieldNumber = 3;
+    private int mapid_;
+    /// <summary>
+    /// 地图id
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Mapid {
+      get { return mapid_; }
+      set {
+        mapid_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CS_MD_CreateRoom);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CS_MD_CreateRoom other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      if (Mapid != other.Mapid) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      if (Mapid != 0) hash ^= Mapid.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+      if (Mapid != 0) {
+        output.WriteRawTag(24);
+        output.WriteInt32(Mapid);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      if (Mapid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Mapid);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CS_MD_CreateRoom other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+      if (other.Mapid != 0) {
+        Mapid = other.Mapid;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+          case 24: {
+            Mapid = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 多人副本, 申请房间(列表)
+  /// </summary>
+  public sealed partial class CS_MD_GetRoomList : pb::IMessage<CS_MD_GetRoomList> {
+    private static readonly pb::MessageParser<CS_MD_GetRoomList> _parser = new pb::MessageParser<CS_MD_GetRoomList>(() => new CS_MD_GetRoomList());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CS_MD_GetRoomList> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[1]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_GetRoomList() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_GetRoomList(CS_MD_GetRoomList other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_GetRoomList Clone() {
+      return new CS_MD_GetRoomList(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CS_MD_GetRoomList);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CS_MD_GetRoomList other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CS_MD_GetRoomList other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 多人副本, 加入房间
+  /// </summary>
+  public sealed partial class CS_MD_EnterRoom : pb::IMessage<CS_MD_EnterRoom> {
+    private static readonly pb::MessageParser<CS_MD_EnterRoom> _parser = new pb::MessageParser<CS_MD_EnterRoom>(() => new CS_MD_EnterRoom());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CS_MD_EnterRoom> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[2]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_EnterRoom() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_EnterRoom(CS_MD_EnterRoom other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+      roomId_ = other.roomId_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_EnterRoom Clone() {
+      return new CS_MD_EnterRoom(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "RoomId" field.</summary>
+    public const int RoomIdFieldNumber = 3;
+    private int roomId_;
+    /// <summary>
+    /// 房间id
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int RoomId {
+      get { return roomId_; }
+      set {
+        roomId_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CS_MD_EnterRoom);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CS_MD_EnterRoom other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      if (RoomId != other.RoomId) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      if (RoomId != 0) hash ^= RoomId.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+      if (RoomId != 0) {
+        output.WriteRawTag(24);
+        output.WriteInt32(RoomId);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      if (RoomId != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(RoomId);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CS_MD_EnterRoom other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+      if (other.RoomId != 0) {
+        RoomId = other.RoomId;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+          case 24: {
+            RoomId = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 多人副本, 离开房间
+  /// </summary>
+  public sealed partial class CS_MD_LeaveRoom : pb::IMessage<CS_MD_LeaveRoom> {
+    private static readonly pb::MessageParser<CS_MD_LeaveRoom> _parser = new pb::MessageParser<CS_MD_LeaveRoom>(() => new CS_MD_LeaveRoom());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CS_MD_LeaveRoom> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[3]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_LeaveRoom() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_LeaveRoom(CS_MD_LeaveRoom other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_LeaveRoom Clone() {
+      return new CS_MD_LeaveRoom(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    /// <summary>
+    ///int32 RoomId=3;    // 房间id (理论上已经处于房间中不需要此字段)
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CS_MD_LeaveRoom);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CS_MD_LeaveRoom other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CS_MD_LeaveRoom other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 多人副本, 开始游戏(创建者)
+  /// </summary>
+  public sealed partial class CS_MD_BeginDup : pb::IMessage<CS_MD_BeginDup> {
+    private static readonly pb::MessageParser<CS_MD_BeginDup> _parser = new pb::MessageParser<CS_MD_BeginDup>(() => new CS_MD_BeginDup());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<CS_MD_BeginDup> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[4]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_BeginDup() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_BeginDup(CS_MD_BeginDup other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public CS_MD_BeginDup Clone() {
+      return new CS_MD_BeginDup(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    /// <summary>
+    ///int32 RoomId=3;    // 房间id (理论上已经处于房间中不需要此字段)
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as CS_MD_BeginDup);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(CS_MD_BeginDup other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(CS_MD_BeginDup other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 
+  /// 多人副本, 创建房间
+  /// </summary>
+  public sealed partial class SC_MD_CreateRoom : pb::IMessage<SC_MD_CreateRoom> {
+    private static readonly pb::MessageParser<SC_MD_CreateRoom> _parser = new pb::MessageParser<SC_MD_CreateRoom>(() => new SC_MD_CreateRoom());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<SC_MD_CreateRoom> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[5]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_CreateRoom() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_CreateRoom(SC_MD_CreateRoom other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+      mapid_ = other.mapid_;
+      roomId_ = other.roomId_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_CreateRoom Clone() {
+      return new SC_MD_CreateRoom(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "Mapid" field.</summary>
+    public const int MapidFieldNumber = 3;
+    private int mapid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Mapid {
+      get { return mapid_; }
+      set {
+        mapid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "RoomId" field.</summary>
+    public const int RoomIdFieldNumber = 4;
+    private int roomId_;
+    /// <summary>
+    /// 房间id (服务端分配)
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int RoomId {
+      get { return roomId_; }
+      set {
+        roomId_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as SC_MD_CreateRoom);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(SC_MD_CreateRoom other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      if (Mapid != other.Mapid) return false;
+      if (RoomId != other.RoomId) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      if (Mapid != 0) hash ^= Mapid.GetHashCode();
+      if (RoomId != 0) hash ^= RoomId.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+      if (Mapid != 0) {
+        output.WriteRawTag(24);
+        output.WriteInt32(Mapid);
+      }
+      if (RoomId != 0) {
+        output.WriteRawTag(32);
+        output.WriteInt32(RoomId);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      if (Mapid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Mapid);
+      }
+      if (RoomId != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(RoomId);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(SC_MD_CreateRoom other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+      if (other.Mapid != 0) {
+        Mapid = other.Mapid;
+      }
+      if (other.RoomId != 0) {
+        RoomId = other.RoomId;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+          case 24: {
+            Mapid = input.ReadInt32();
+            break;
+          }
+          case 32: {
+            RoomId = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 多人副本, 申请房间(列表)
+  /// </summary>
+  public sealed partial class SC_MD_GetRoomList : pb::IMessage<SC_MD_GetRoomList> {
+    private static readonly pb::MessageParser<SC_MD_GetRoomList> _parser = new pb::MessageParser<SC_MD_GetRoomList>(() => new SC_MD_GetRoomList());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<SC_MD_GetRoomList> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[6]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_GetRoomList() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_GetRoomList(SC_MD_GetRoomList other) : this() {
+      roomInfos_ = other.roomInfos_.Clone();
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_GetRoomList Clone() {
+      return new SC_MD_GetRoomList(this);
+    }
+
+    /// <summary>Field number for the "RoomInfos" field.</summary>
+    public const int RoomInfosFieldNumber = 1;
+    private static readonly pb::FieldCodec<global::MultiDup.SC_MD_GetRoomList.Types.RoomInfo> _repeated_roomInfos_codec
+        = pb::FieldCodec.ForMessage(10, global::MultiDup.SC_MD_GetRoomList.Types.RoomInfo.Parser);
+    private readonly pbc::RepeatedField<global::MultiDup.SC_MD_GetRoomList.Types.RoomInfo> roomInfos_ = new pbc::RepeatedField<global::MultiDup.SC_MD_GetRoomList.Types.RoomInfo>();
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public pbc::RepeatedField<global::MultiDup.SC_MD_GetRoomList.Types.RoomInfo> RoomInfos {
+      get { return roomInfos_; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as SC_MD_GetRoomList);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(SC_MD_GetRoomList other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if(!roomInfos_.Equals(other.roomInfos_)) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      hash ^= roomInfos_.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      roomInfos_.WriteTo(output, _repeated_roomInfos_codec);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      size += roomInfos_.CalculateSize(_repeated_roomInfos_codec);
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(SC_MD_GetRoomList other) {
+      if (other == null) {
+        return;
+      }
+      roomInfos_.Add(other.roomInfos_);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 10: {
+            roomInfos_.AddEntriesFrom(input, _repeated_roomInfos_codec);
+            break;
+          }
+        }
+      }
+    }
+
+    #region Nested types
+    /// <summary>Container for nested types declared in the SC_MD_GetRoomList message type.</summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static partial class Types {
+      public sealed partial class RoomInfo : pb::IMessage<RoomInfo> {
+        private static readonly pb::MessageParser<RoomInfo> _parser = new pb::MessageParser<RoomInfo>(() => new RoomInfo());
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public static pb::MessageParser<RoomInfo> Parser { get { return _parser; } }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public static pbr::MessageDescriptor Descriptor {
+          get { return global::MultiDup.SC_MD_GetRoomList.Descriptor.NestedTypes[0]; }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        pbr::MessageDescriptor pb::IMessage.Descriptor {
+          get { return Descriptor; }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public RoomInfo() {
+          OnConstruction();
+        }
+
+        partial void OnConstruction();
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public RoomInfo(RoomInfo other) : this() {
+          roomId_ = other.roomId_;
+          mapid_ = other.mapid_;
+          playerUids_ = other.playerUids_.Clone();
+          battleserver_ = other.battleserver_;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public RoomInfo Clone() {
+          return new RoomInfo(this);
+        }
+
+        /// <summary>Field number for the "RoomId" field.</summary>
+        public const int RoomIdFieldNumber = 1;
+        private int roomId_;
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public int RoomId {
+          get { return roomId_; }
+          set {
+            roomId_ = value;
+          }
+        }
+
+        /// <summary>Field number for the "Mapid" field.</summary>
+        public const int MapidFieldNumber = 2;
+        private int mapid_;
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public int Mapid {
+          get { return mapid_; }
+          set {
+            mapid_ = value;
+          }
+        }
+
+        /// <summary>Field number for the "PlayerUids" field.</summary>
+        public const int PlayerUidsFieldNumber = 3;
+        private static readonly pb::FieldCodec<string> _repeated_playerUids_codec
+            = pb::FieldCodec.ForString(26);
+        private readonly pbc::RepeatedField<string> playerUids_ = new pbc::RepeatedField<string>();
+        /// <summary>
+        ///??
+        /// </summary>
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public pbc::RepeatedField<string> PlayerUids {
+          get { return playerUids_; }
+        }
+
+        /// <summary>Field number for the "battleserver" field.</summary>
+        public const int BattleserverFieldNumber = 4;
+        private string battleserver_ = "";
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public string Battleserver {
+          get { return battleserver_; }
+          set {
+            battleserver_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override bool Equals(object other) {
+          return Equals(other as RoomInfo);
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public bool Equals(RoomInfo other) {
+          if (ReferenceEquals(other, null)) {
+            return false;
+          }
+          if (ReferenceEquals(other, this)) {
+            return true;
+          }
+          if (RoomId != other.RoomId) return false;
+          if (Mapid != other.Mapid) return false;
+          if(!playerUids_.Equals(other.playerUids_)) return false;
+          if (Battleserver != other.Battleserver) return false;
+          return true;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override int GetHashCode() {
+          int hash = 1;
+          if (RoomId != 0) hash ^= RoomId.GetHashCode();
+          if (Mapid != 0) hash ^= Mapid.GetHashCode();
+          hash ^= playerUids_.GetHashCode();
+          if (Battleserver.Length != 0) hash ^= Battleserver.GetHashCode();
+          return hash;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public override string ToString() {
+          return pb::JsonFormatter.ToDiagnosticString(this);
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void WriteTo(pb::CodedOutputStream output) {
+          if (RoomId != 0) {
+            output.WriteRawTag(8);
+            output.WriteInt32(RoomId);
+          }
+          if (Mapid != 0) {
+            output.WriteRawTag(16);
+            output.WriteInt32(Mapid);
+          }
+          playerUids_.WriteTo(output, _repeated_playerUids_codec);
+          if (Battleserver.Length != 0) {
+            output.WriteRawTag(34);
+            output.WriteString(Battleserver);
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public int CalculateSize() {
+          int size = 0;
+          if (RoomId != 0) {
+            size += 1 + pb::CodedOutputStream.ComputeInt32Size(RoomId);
+          }
+          if (Mapid != 0) {
+            size += 1 + pb::CodedOutputStream.ComputeInt32Size(Mapid);
+          }
+          size += playerUids_.CalculateSize(_repeated_playerUids_codec);
+          if (Battleserver.Length != 0) {
+            size += 1 + pb::CodedOutputStream.ComputeStringSize(Battleserver);
+          }
+          return size;
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void MergeFrom(RoomInfo other) {
+          if (other == null) {
+            return;
+          }
+          if (other.RoomId != 0) {
+            RoomId = other.RoomId;
+          }
+          if (other.Mapid != 0) {
+            Mapid = other.Mapid;
+          }
+          playerUids_.Add(other.playerUids_);
+          if (other.Battleserver.Length != 0) {
+            Battleserver = other.Battleserver;
+          }
+        }
+
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+        public void MergeFrom(pb::CodedInputStream input) {
+          uint tag;
+          while ((tag = input.ReadTag()) != 0) {
+            switch(tag) {
+              default:
+                input.SkipLastField();
+                break;
+              case 8: {
+                RoomId = input.ReadInt32();
+                break;
+              }
+              case 16: {
+                Mapid = input.ReadInt32();
+                break;
+              }
+              case 26: {
+                playerUids_.AddEntriesFrom(input, _repeated_playerUids_codec);
+                break;
+              }
+              case 34: {
+                Battleserver = input.ReadString();
+                break;
+              }
+            }
+          }
+        }
+
+      }
+
+    }
+    #endregion
+
+  }
+
+  /// <summary>
+  /// 多人副本, 加入房间
+  /// </summary>
+  public sealed partial class SC_MD_EnterRoom : pb::IMessage<SC_MD_EnterRoom> {
+    private static readonly pb::MessageParser<SC_MD_EnterRoom> _parser = new pb::MessageParser<SC_MD_EnterRoom>(() => new SC_MD_EnterRoom());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<SC_MD_EnterRoom> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[7]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_EnterRoom() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_EnterRoom(SC_MD_EnterRoom other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+      mapid_ = other.mapid_;
+      ip_ = other.ip_;
+      port_ = other.port_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_EnterRoom Clone() {
+      return new SC_MD_EnterRoom(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    /// <summary>
+    /// (新进id)
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "Mapid" field.</summary>
+    public const int MapidFieldNumber = 3;
+    private int mapid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Mapid {
+      get { return mapid_; }
+      set {
+        mapid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Ip" field.</summary>
+    public const int IpFieldNumber = 4;
+    private string ip_ = "";
+    /// <summary>
+    /// 战斗服务器ip
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Ip {
+      get { return ip_; }
+      set {
+        ip_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    /// <summary>Field number for the "Port" field.</summary>
+    public const int PortFieldNumber = 5;
+    private int port_;
+    /// <summary>
+    /// 战斗服务器端口
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Port {
+      get { return port_; }
+      set {
+        port_ = value;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as SC_MD_EnterRoom);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(SC_MD_EnterRoom other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      if (Mapid != other.Mapid) return false;
+      if (Ip != other.Ip) return false;
+      if (Port != other.Port) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      if (Mapid != 0) hash ^= Mapid.GetHashCode();
+      if (Ip.Length != 0) hash ^= Ip.GetHashCode();
+      if (Port != 0) hash ^= Port.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+      if (Mapid != 0) {
+        output.WriteRawTag(24);
+        output.WriteInt32(Mapid);
+      }
+      if (Ip.Length != 0) {
+        output.WriteRawTag(34);
+        output.WriteString(Ip);
+      }
+      if (Port != 0) {
+        output.WriteRawTag(40);
+        output.WriteInt32(Port);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      if (Mapid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Mapid);
+      }
+      if (Ip.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Ip);
+      }
+      if (Port != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Port);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(SC_MD_EnterRoom other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+      if (other.Mapid != 0) {
+        Mapid = other.Mapid;
+      }
+      if (other.Ip.Length != 0) {
+        Ip = other.Ip;
+      }
+      if (other.Port != 0) {
+        Port = other.Port;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+          case 24: {
+            Mapid = input.ReadInt32();
+            break;
+          }
+          case 34: {
+            Ip = input.ReadString();
+            break;
+          }
+          case 40: {
+            Port = input.ReadInt32();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 多人副本, 离开房间
+  /// </summary>
+  public sealed partial class SC_MD_LeaveRoom : pb::IMessage<SC_MD_LeaveRoom> {
+    private static readonly pb::MessageParser<SC_MD_LeaveRoom> _parser = new pb::MessageParser<SC_MD_LeaveRoom>(() => new SC_MD_LeaveRoom());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<SC_MD_LeaveRoom> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[8]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_LeaveRoom() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_LeaveRoom(SC_MD_LeaveRoom other) : this() {
+      zoneid_ = other.zoneid_;
+      uid_ = other.uid_;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_LeaveRoom Clone() {
+      return new SC_MD_LeaveRoom(this);
+    }
+
+    /// <summary>Field number for the "Zoneid" field.</summary>
+    public const int ZoneidFieldNumber = 1;
+    private int zoneid_;
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int Zoneid {
+      get { return zoneid_; }
+      set {
+        zoneid_ = value;
+      }
+    }
+
+    /// <summary>Field number for the "Uid" field.</summary>
+    public const int UidFieldNumber = 2;
+    private string uid_ = "";
+    /// <summary>
+    /// (离开id)
+    /// </summary>
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public string Uid {
+      get { return uid_; }
+      set {
+        uid_ = pb::ProtoPreconditions.CheckNotNull(value, "value");
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as SC_MD_LeaveRoom);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(SC_MD_LeaveRoom other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      if (Zoneid != other.Zoneid) return false;
+      if (Uid != other.Uid) return false;
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      if (Zoneid != 0) hash ^= Zoneid.GetHashCode();
+      if (Uid.Length != 0) hash ^= Uid.GetHashCode();
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+      if (Zoneid != 0) {
+        output.WriteRawTag(8);
+        output.WriteInt32(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        output.WriteRawTag(18);
+        output.WriteString(Uid);
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      if (Zoneid != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeInt32Size(Zoneid);
+      }
+      if (Uid.Length != 0) {
+        size += 1 + pb::CodedOutputStream.ComputeStringSize(Uid);
+      }
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(SC_MD_LeaveRoom other) {
+      if (other == null) {
+        return;
+      }
+      if (other.Zoneid != 0) {
+        Zoneid = other.Zoneid;
+      }
+      if (other.Uid.Length != 0) {
+        Uid = other.Uid;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+          case 8: {
+            Zoneid = input.ReadInt32();
+            break;
+          }
+          case 18: {
+            Uid = input.ReadString();
+            break;
+          }
+        }
+      }
+    }
+
+  }
+
+  /// <summary>
+  /// 多人副本, 开始游戏(创建者)
+  /// </summary>
+  public sealed partial class SC_MD_BeginDup : pb::IMessage<SC_MD_BeginDup> {
+    private static readonly pb::MessageParser<SC_MD_BeginDup> _parser = new pb::MessageParser<SC_MD_BeginDup>(() => new SC_MD_BeginDup());
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pb::MessageParser<SC_MD_BeginDup> Parser { get { return _parser; } }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public static pbr::MessageDescriptor Descriptor {
+      get { return global::MultiDup.MultiDupReflection.Descriptor.MessageTypes[9]; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    pbr::MessageDescriptor pb::IMessage.Descriptor {
+      get { return Descriptor; }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_BeginDup() {
+      OnConstruction();
+    }
+
+    partial void OnConstruction();
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_BeginDup(SC_MD_BeginDup other) : this() {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public SC_MD_BeginDup Clone() {
+      return new SC_MD_BeginDup(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override bool Equals(object other) {
+      return Equals(other as SC_MD_BeginDup);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public bool Equals(SC_MD_BeginDup other) {
+      if (ReferenceEquals(other, null)) {
+        return false;
+      }
+      if (ReferenceEquals(other, this)) {
+        return true;
+      }
+      return true;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override int GetHashCode() {
+      int hash = 1;
+      return hash;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public override string ToString() {
+      return pb::JsonFormatter.ToDiagnosticString(this);
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void WriteTo(pb::CodedOutputStream output) {
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public int CalculateSize() {
+      int size = 0;
+      return size;
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(SC_MD_BeginDup other) {
+      if (other == null) {
+        return;
+      }
+    }
+
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
+    public void MergeFrom(pb::CodedInputStream input) {
+      uint tag;
+      while ((tag = input.ReadTag()) != 0) {
+        switch(tag) {
+          default:
+            input.SkipLastField();
+            break;
+        }
+      }
+    }
+
+  }
+
+  #endregion
+
+}
+
+#endregion Designer generated code

+ 3 - 1
CSserver/clientTest/Program_Chat.cs

@@ -123,7 +123,9 @@ namespace clientTest.chat
         {
 
             var port = 6000;
-            var endPoint = new IPEndPoint(IPAddress.Parse("192.168.10.86"), port);
+
+            var endPoint = new IPEndPoint(IPAddress.Parse("192.168.10.17"), port);
+            //var endPoint = new IPEndPoint(IPAddress.Loopback, port);
             //var endPoint = new IPEndPoint(IPAddress.Parse("115.159.121.129"), port);
 
             using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))

+ 316 - 0
CSserver/clientTest/Program_MultiDup.cs

@@ -0,0 +1,316 @@
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Channels;
+using System.Linq;
+using System.Net.Http;
+using System.Collections.Generic;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Diagnostics;
+using ProtoDataBuff;
+using MultiDup;
+using pb = global::Google.Protobuf;
+
+namespace clientTest.multiDup
+
+{
+
+    class Program
+    {
+        static readonly Random r = new Random();
+
+        /// <summary>
+        /// 消息分发
+        /// </summary>
+        static private Dictionary<eProtocalCommand, Action<sSocketData>> callbacks = new Dictionary<eProtocalCommand, Action<sSocketData>>();
+        static  void Main(string[] args)
+        {
+
+            callbacks.Add(eProtocalCommand.ScMdGetRoomList, On_update);
+            callbacks.Add(eProtocalCommand.ScMdEnterRoom, On_enter);
+            callbacks.Add(eProtocalCommand.ScMdBeginDup, On_begin);
+            callbacks.Add(eProtocalCommand.ScMdLeaveRoom, On_GameOver);
+            callbacks.Add(eProtocalCommand.ScMdCreateRoom, On_CreateRoom);
+            var n = 1;
+            var list = new Task[n];
+            for (int i = 0; i < n; i++)
+            {
+                list[i] = Task.Run(async () => await send());
+            }
+              Task.Run(Dispatch);
+
+            Task.WaitAll(list);
+        }
+
+
+        #region 收到消息处理
+        /// <summary>
+        /// 更新房间列表
+        /// </summary>
+        static void On_update(sSocketData data)
+        {
+            Console.WriteLine("最新房间列表: {}");
+        }
+        /// <summary>
+        /// 客户端进入房间
+        /// </summary>
+        static void On_CreateRoom(sSocketData data)
+        {
+            Console.WriteLine("xxx创建了房间.");
+        }
+        /// <summary>
+        /// 客户端进入房间
+        /// </summary>
+        static void On_begin(sSocketData data)
+        {
+            Console.WriteLine("xxx开始了游戏.");
+        }
+        /// <summary>
+        /// 客户端进入房间
+        /// </summary>
+        static void On_enter(sSocketData data)
+        {
+            Console.WriteLine("xx进入了房间.");
+        }
+        /// <summary>
+        /// 处理客户端上报伤害请求
+        /// </summary>
+        static void On_GameOver(sSocketData data)
+        {
+            Console.WriteLine("xx已离开房间! ");
+            Task.Delay(3000).ContinueWith(t => Environment.Exit(0));
+        }
+
+        #endregion
+
+        static async Task send()
+        {
+
+            var port = 6004;
+
+            var endPoint = new IPEndPoint(IPAddress.Parse("192.168.10.17"), port);
+            //var endPoint = new IPEndPoint(IPAddress.Loopback, port);
+            //var endPoint = new IPEndPoint(IPAddress.Parse("115.159.121.129"), port);
+
+            using (var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
+            {
+                try
+                {
+                    await client.ConnectAsync(endPoint);
+                }
+                catch (Exception ee)
+                {
+                    Debug.WriteLine(ee.Message);
+                }
+
+
+                var t = Task.Run(() => recv(client));                    // 开启recv监听协程
+                try
+                {
+                    // 连上来之后, 创建房间, 开始游戏, 退出房间
+                     CreateRoom(client).Wait();
+                    Thread.Sleep(r.Next(1000, 3000));
+                    await BeginGame(client);
+                    Thread.Sleep(r.Next(1000, 3000));
+                    await LeaveGame(client);
+                    Thread.Sleep(r.Next(1000, 3000));
+                    Console.ReadKey();
+                }
+                catch (Exception e)
+                {
+                    client.Close();
+                }
+                finally
+                {
+                    client.Close();
+                }
+
+            }
+        }
+        /// <summary>
+        /// 创建房间
+        /// </summary>
+        /// <param name="Sock"></param>
+        /// <returns></returns>
+        async static Task CreateRoom(Socket Sock)
+        {
+            try
+            {
+                var msg = new CS_MD_CreateRoom() { };
+                var data = SocketDataToBytes(BytesToSocketData(eProtocalCommand.CsMdCreateRoom, IMsg2Bytes(msg)));
+                await Sock.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
+            }
+            catch (Exception e)
+            {
+                Sock.Close();
+            }
+        }
+        /// <summary>
+        /// 开始游戏
+        /// </summary>
+        /// <param name="sock"></param>
+        /// <returns></returns>
+        async static Task BeginGame(Socket sock)
+        {
+            var msg = new CS_MD_BeginDup() { };
+
+            var data = SocketDataToBytes(BytesToSocketData(eProtocalCommand.CsMdBeginDup, IMsg2Bytes(msg)));
+            await sock.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
+        }
+
+        /// <summary>
+        /// 离开游戏
+        /// </summary>
+        /// <param name="sock"></param>
+        /// <returns></returns>
+        async static Task LeaveGame(Socket sock)
+        {
+            var msg = new CS_MD_LeaveRoom() { };
+
+            var data = SocketDataToBytes(BytesToSocketData(eProtocalCommand.CsMdLeaveRoom, IMsg2Bytes(msg)));
+            await sock.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
+        }
+
+
+        #region 消息收发
+
+        static byte[] IMsg2Bytes(pb::IMessage msg)
+        {
+            using var ms = new MemoryStream();
+            using var goutstream = new pb::CodedOutputStream(ms);
+            msg.WriteTo(goutstream);
+            goutstream.Flush();
+            ms.Seek(0, SeekOrigin.Begin);
+            return ms.ToArray();
+        }
+
+
+
+        static async void Dispatch()
+        {
+            while (true)
+            {
+                var msg = await recvDataBuffer.Reader.ReadAsync();
+                if (callbacks.ContainsKey(msg._protocallType))
+                {
+                    callbacks[msg._protocallType](msg);
+                }
+                else
+                {
+                    // 未找到消息处理逻辑
+                    Console.WriteLine("未识别的消息类型:" + msg._protocallType.ToString());
+                }
+            }
+            Console.WriteLine("dispatch 已退出");
+        }
+
+        /// <summary>
+        /// 向客户端写入消息
+        /// </summary>
+        static async void WriteToserver(Socket Sock)
+        {
+            while (true)
+            {
+                var msg = await sendDataBuffer.Reader.ReadAsync();
+                var data = SocketDataToBytes(msg);
+                await Sock.SendAsync(new ArraySegment<byte>(data), SocketFlags.None);
+            }
+        }
+        /// <summary>
+        /// 网络结构转数据
+        /// </summary>
+        /// <param name="tmpSocketData"></param>
+        /// <returns></returns>
+        static private byte[] SocketDataToBytes(sSocketData tmpSocketData)
+        {
+            byte[] _tmpBuff = new byte[tmpSocketData._buffLength];
+            byte[] _tmpBuffLength = BitConverter.GetBytes(tmpSocketData._buffLength);
+            byte[] _tmpDataLenght = BitConverter.GetBytes((UInt16)tmpSocketData._protocallType);
+
+            Array.Copy(_tmpBuffLength, 0, _tmpBuff, 0, Constants.HEAD_DATA_LEN);//缓存总长度
+            Array.Copy(_tmpDataLenght, 0, _tmpBuff, Constants.HEAD_DATA_LEN, Constants.HEAD_TYPE_LEN);//协议类型
+            Array.Copy(tmpSocketData._data, 0, _tmpBuff, Constants.HEAD_LEN, tmpSocketData._dataLength);//协议数据
+
+            return _tmpBuff;
+        }
+
+
+        /// <summary>
+        /// 数据转网络结构
+        /// </summary>
+        /// <param name="_protocalType"></param>
+        /// <param name="_data"></param>
+        /// <returns></returns>
+        static private sSocketData BytesToSocketData(eProtocalCommand _protocalType, byte[] _data)
+        {
+            sSocketData tmpSocketData = new sSocketData();
+            tmpSocketData._buffLength = Constants.HEAD_LEN + _data.Length;
+            tmpSocketData._dataLength = _data.Length;
+            tmpSocketData._protocallType = _protocalType;
+            tmpSocketData._data = _data;
+            return tmpSocketData;
+        }
+
+
+        /// <summary>
+        /// 接收buffer
+        /// </summary>
+        static private Channel<sSocketData> recvDataBuffer = Channel.CreateUnbounded<sSocketData>();
+        /// <summary>
+        /// 发送buffer
+        /// </summary>
+        static private Channel<sSocketData> sendDataBuffer = Channel.CreateUnbounded<sSocketData>();
+
+        /// <summary>
+        /// 接收客户端发来的信息,客户端套接字对象
+        /// </summary>
+        /// <param name="socketclientpara"></param>    
+        static async void recv(Socket socketServer)
+        {
+            socketServer.ReceiveTimeout = 800;                                             // 接收等待超时时间设为800毫秒
+            var _databuffer = new DataBuffer();
+            byte[] arrServerRecMsg = new byte[4096];                                       // 创建一个内存缓冲区,其大小为4k字节  
+            while (true)
+            {
+                try
+                {
+                    var length = await socketServer.ReceiveAsync(new ArraySegment<byte>(arrServerRecMsg), SocketFlags.None);   // 将接收到的信息存入到内存缓冲区,并返回其字节数组的长度    
+                    if (length <= 0)                                                      // 视为客户端已经close连接
+                    {
+                        break;
+                    }
+                    _databuffer.AddBuffer(arrServerRecMsg, length);                        //将收到的数据添加到缓存器中
+                    while (_databuffer.GetData(out sSocketData _socketData))                         //取出一条完整数据
+                    {
+                        Console.WriteLine("recv: " + _socketData._protocallType);
+                        await recvDataBuffer.Writer.WriteAsync(_socketData);                 // 放入channel
+                    }
+                    Array.Clear(arrServerRecMsg, 0, length);
+                }
+                catch (SocketException e)
+                {
+                    if (e.ErrorCode == 10060)                                          // 超时的时候错误号码是10060
+                    {
+                        continue;                                                      // 继续等待  
+                    }
+                    break;
+                }
+                catch (Exception)
+                {
+                    break;
+                }
+            }
+            Console.WriteLine("recv 已退出");
+            socketServer.Close();                                                     // 关闭之前accept出来的和客户端进行通信的套接字 
+        }
+
+        #endregion
+    }
+
+
+}

+ 1 - 1
CSserver/clientTest/clientTest.csproj

@@ -3,7 +3,7 @@
   <PropertyGroup>
     <OutputType>Exe</OutputType>
     <TargetFramework>net5.0</TargetFramework>
-    <StartupObject>clientTest.chat.Program</StartupObject>
+    <StartupObject>clientTest.multiDup.Program</StartupObject>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

+ 9 - 3
CSserver/csserver.sln

@@ -1,7 +1,7 @@
 
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31005.135
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31825.309
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ChatServer", "csserver\ChatServer.csproj", "{ADB21DA6-E0B4-4218-85F4-59351E635488}"
 EndProject
@@ -15,7 +15,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lib1", "Lib1\Lib1.csproj",
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Libs", "Libs", "{02C64572-AF0B-4AE0-8685-3836A0FD1C1E}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DataTransfer", "DataTransfer\DataTransfer.csproj", "{201B65E5-35B6-4AF0-B4DF-8334484BC804}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DataTransfer", "DataTransfer\DataTransfer.csproj", "{201B65E5-35B6-4AF0-B4DF-8334484BC804}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MultiDup", "MultiDup\MultiDup.csproj", "{25F90738-ECAE-40CC-883F-86A75E6C253B}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -47,6 +49,10 @@ Global
 		{201B65E5-35B6-4AF0-B4DF-8334484BC804}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{201B65E5-35B6-4AF0-B4DF-8334484BC804}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{201B65E5-35B6-4AF0-B4DF-8334484BC804}.Release|Any CPU.Build.0 = Release|Any CPU
+		{25F90738-ECAE-40CC-883F-86A75E6C253B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{25F90738-ECAE-40CC-883F-86A75E6C253B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{25F90738-ECAE-40CC-883F-86A75E6C253B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{25F90738-ECAE-40CC-883F-86A75E6C253B}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE