C# 语言进阶(十三)网络 DLL 库分层设计
本篇核心知识点游戏网络通信完整分层、公共网络 DLL库分层设计、Message 消息基类与多态子类、Packet 数据包粘包解决方案、Session 会话收发层、异步拆包完整逻辑、消息队列线程安全、服务端 / 客户端双端架构、Unity 对接网络库、消息命令约定、多态序列化一、整体工程架构总览1. 项目分层结构公共底层 DLL 库MyNet.dll存放两端共用代码Packet 数据包、GameMessage 消息基类、Session 会话层控制台服务端、Unity 客户端同时引用一套代码复用消除重复开发。控制台登录服务端LoginServer依赖 DLL监听端口、接收客户端连接、消息分发、业务逻辑处理。Unity 客户端工程DLL 放置 Assets 目录NetManager 单例网络管理器、UI 登录面板、主线程消息队列规避 Unity 多线程报错。2. 分层职责划分底层 Packet解决 TCP 粘包封装 4 字节包头 消息包体GameMessage消息父类多态序列化 / 反序列化统一通信数据载体Session 会话底层 Socket 异步收发、完整拆包、断连回调、消息委托转发上层管理器服务端 LoginServer / Unity NetManager处理业务、存储消息队列、分发逻辑。通俗类比Packet 快递包装盒带长度标签区分包裹GameMessage 包裹内货物Session 快递员负责收发服务端 / 客户端 收发货双方。二、Packet 数据包粘包核心解决方案1. 核心概念TCP 是无边界字节流多条消息会粘连在一起无法区分一条消息起止Packet 固定4 字节 int 包头存储包体长度接收端先读包头再读取对应长度包体彻底解决粘包 / 拆包。2. Packet 成员字段byte[] HeadBuf new byte[4]固定 4 字节包头存放包体字节长度byte[] BodyBuf存放序列化后的 GameMessage 二进制数据两个构造函数发送构造传入 GameMessage自动序列化填充 BodyBuf计算长度写入 HeadBuf空接收构造用于异步接收时缓存分段字节辅助方法GetFullBuffer()拼接包头 包体返回完整待发送字节数组Reset()重置方法清空缓冲区复用 Packet 减少频繁 new 内存开销。3. 关键拆包 Bug 修复知识点问题场景单次异步 Receive 仅读取部分包头如只读到 2 字节还差 2 字节直接解析长度会数值错乱程序崩溃。修复逻辑增加包头接收计数int headReadLen 0循环累加收到的字节只有headReadLen 4时才解析包头获取 Body 长度包体同样增加读取计数未达到指定长度时持续异步接收补齐剩余字节完整包解析完成后调用 Reset 重置缓冲区准备接收下一条消息。代码示例Packet 核心结构public class MessagePacket{ // 固定4字节包头存储包体长度 public byte[] HeadBuf new byte[4]; public byte[] BodyBuf; // 接收累计读取长度 public int HeadReadLen 0; public int BodyReadLen 0; // 发送用构造 public MessagePacket(GameMessage msg){ BodyBuf msg.ToBytes(); // 将包体长度写入4字节包头 BitConverter.GetBytes(BodyBuf.Length).CopyTo(HeadBuf, 0); } // 空构造接收数据使用 public MessagePacket() { } // 拼接包头包体得到完整发送字节 public byte[] GetFullBuffer(){ Listbyte full new Listbyte(HeadBuf); full.AddRange(BodyBuf); return full.ToArray(); } // 重置缓冲区复用对象减少GC public void Reset(){ HeadReadLen 0; BodyReadLen 0; BodyBuf null; } }三、GameMessage 消息基类多态序列化1. 基础通用字段所有消息共用int Uid玩家唯一 ID登录前为临时空值登录成功服务下发玩家 Uidint MainCmd主命令区分业务模块登录 / 战斗 / 聊天int SubCmd子命令区分具体操作规范约定客户端发服务端 奇数 (1001)服务返回客户端 偶数 (1002)int Result操作结果码0 成功、1 账号错、2 密码错等替代 bool 支持多状态。2. 序列化 / 反序列化核心机制概念序列化将对象所有字段转为连续 byte 数组网络传输只能发送字节反序列化收到字节数组还原对象字段。多态强制规则父类virtual byte[] ToBytes()序列化通用 Uid、Cmd 等字段子类C2SLogin/S2LoginResp必须override ToBytes()子类序列化先调用 base.ToBytes ()写入父类通用字节再追加自身业务字段反序列化同理先解析父类字节再读取子类专属数据。3. 底层字节工具函数父类内置静态读写工具统一处理 int/string/floatWriteInt(Listbyte buf, int val)写入 4 字节整数WriteString(Listbyte buf, string str)先写入字符串长度再写入字符字节解决不定长字符串边界ReadInt(byte[] data, ref int offset)从指定偏移读取 int偏移自动后移 4ReadString(byte[] data, ref int offset)先读长度再读取字符。4. 典型子类登录请求消息 C2SLogin// 客户端→服务端 登录消息 SubCmd1001 public class C2SLogin : GameMessage{ public string Account; public string Password; public override byte[] ToBytes(){ // 先序列化父类通用字段 Listbyte buffer new Listbyte(base.ToBytes()); // 追加子类账号、密码 WriteString(buffer, Account); return buffer.ToArray(); } // 反序列化从字节还原账号密码 public override void FromBuffer(byte[] data){ int offset 0; base.FromBuffer(data, ref offset); Account ReadString(data, ref offset); Password ReadString(data, ref offset); } }5. 高频坑点仅反序列化父类丢失子类字段问题收到字节直接 new GameMessage 反序列化只会解析 Uid/Cmd账号密码等子类数据丢失。解决方案先通过包头解析的消息 SubCmd 判断消息真实子类泛型工具T GameMessage.FromBytesT(byte[] buf)约束 T 有无参构造根据命令 new 对应子类调用子类 FromBuffer 完整解析全部字段。6. 流量优化拓展int 数字直接 4 字节二进制远优于字符串传输数字 123456 字符串占 6 字节二进制仅 4 字节大幅降低实时游戏流量消耗早期 2G/3G 时代极致优化char 替代 byte、字段对齐压缩现代项目仅需基础二进制序列化。四、Session 会话收发层底层 Socket 封装1. 核心职责封装 Socket 与 NetworkStream异步 BeginReceive 非阻塞收发完整包头、包体分段拆包逻辑自动处理粘包消息委托回调完整 Packet 解析完成后转发上层处理网络异常 / 主动断连触发虚函数OnDisconnect()供子类重写弹窗提示对外SendMessage(GameMessage msg)接口自动打包 Packet 发送。2. 核心成员Socket _socket底层套接字NetworkStream _stream网络读写流Actionbyte[], Session OnMsgReceive消息回调委托protected virtual void OnDisconnect()断连虚回调MessagePacket _recvPacket复用接收包缓冲区。3. 异步拆包完整流程初始化空 Packet调用_stream.BeginReceive()异步读取 4 字节包头异步回调判断本次读取字节数量累加 HeadReadLen未凑齐 4 字节再次发起异步读取补齐凑齐 4 字节后 BitConverter 解析包体长度初始化 BodyBuf异步循环读取包体累加 BodyReadLen 至目标长度包体完整触发委托将包体字节 当前 Session 传给上层调用 Packet.Reset 重置开启下一轮接收循环。4. 断开连接执行顺序触发 OnDisconnect 虚方法子类重写弹窗、清理玩家数据关闭 NetworkStream 流关闭 Socket 释放端口资源分层解耦设计Session 只负责底层字节收发、拆包不处理任何业务逻辑收到完整消息通过委托交给上层 LoginServer/NetManager底层与业务完全隔离。五、服务端 LoginServer 实现1. 运行流程初始化 Socket Bind 绑定端口、Listen 监听客户端连接循环Accept()等待客户端接入新连接创建 Session 对象绑定消息处理委托、开启异步接收多线程消息队列加锁保证并发安全子线程收到消息加锁入队主线循环遍历队列根据 SubCmd 分发业务逻辑收到登录消息校验账号课程写死模拟数据库构造 S2CLoginResp 回复客户端维护 Session 字典存储所有在线客户端会话支持点对点消息回复。2. 多线程安全队列规范private QueueMsgSessionPair _msgQueue new QueueMsgSessionPair(); private readonly object _queueLock new object; // 消息载体同时存消息来源Session服务端多客户端必备 public struct MsgSessionPair{ public Game Msg; public Session Session; } // 入队子线程 public void Enqueue(byte[] body, Session session){ lock(_queueLock){ // 根据SubCmd实例对应子类反序列化 GameMessage msg Game.FromBytesC2SLogin(body); _msgQueue.Enqueue(new MsgSessionPair(){Msg msg, Session session}); } } // 主线循环处理 public void ProcessMsg(){ lock(_queueLock){ while(_msg.Count 0){ var pair _msg.Dequeue(); DispatchMsg(pair.Msg, pair.Session); } } } // 命令分发 void DispatchMsg(GameMessage msg, Session session){ switch(msg.SubCmd){ case 1001: C2SLogin loginMsg msg as C2SLogin; // 模拟数据库校验账号 S2LoginResp resp new S2LoginResp(); resp.SubCmd 1002; if(loginMsg.Account 123456 loginMsg.Password 123456) resp.Result 0; // 成功 else resp.Result 1; // 密码错误 session.SendMessage(resp); break; } }3. 服务端特有设计消息队列同时保存消息 来源 Session回复消息时精准发给对应客户端多客户端长连接并发每个客户端独立 Session 互不干扰。六、Unity 客户端分层实现1. 工程配置规范DLL 输出路径指定 Unity Assets 文件夹项目自动识别新建 Net 空 GameObject 挂载 NetManager 单例UI 层独立 LoginUI 面板按钮绑定登录发送逻辑。2. NetManager 全局网络单例核心限制Unity 禁止子线程操作 UI、GameObject解决方案Session 异步子线程收到消息加锁存入 UI 安全消息队列在 MonoBehaviour.Update 主线循环批量取出消息处理所有弹窗、界面跳转、物体操作仅在 Update 执行。3. LoginSession 子类重写断连回调继承底层 Session重写OnDisconnect()绑定事件委托通知 UI 弹出断网提示观察者模式解耦。4. UI 层调用逻辑LoginUI 按钮点击直接调用NetManager.Instance.SendLogin()构造 C2SLogin 消息发送Update 主线收到 1002 登录回执根据 Result 值弹窗提示登录成功 / 密码错误。七、消息命令统一设计规范主 Cmd区分大业务模块1000 登录、2000 战斗、3000 聊天子 SubCmd 细分操作C2S 客户端发奇数 1001 登录请求、1003 注册请求S2C 服务回偶数 1002 登录结果、1004 注册结果Result 结果码统一0 成功、1 密码错误、2 账号不存在拓展无需修改命令号。八、项目调试与常见 Bug 排错粘包解析错乱未补齐包头 / 包体字节修复累加读取计数消息子类字段丢失仅反序列化 GameMessage 基类需根据 SubCmd 实例对应子类Unity 子线程报错未中转主线消息队列所有 UI 逻辑放 Update连接断开无提示未重写 Session.OnDisconnect 虚回调服务端无法回复客户端消息队列未绑定来源 Session频繁 GC 卡顿Packet 未复用循环大量 new增加 Reset 复用机制。九、拓展1 TCP 粘包成因与包头 4 字节 Packet 行业标准解决方案2 GameMessage 多态序列化原理子类必须 base 父类的核心原因3 Session 分层设计优势底层收发与业务解耦两端共用 DLL4 Unity 线程限制、主线消息队列解决方案5 服务端多客户端如何精准回复消息消息绑定 Session6 异步 BeginReceive 相比同步 Receive 性能优势7 二进制序列化对比 JSON 流量优势8 长连接工作流程、TCP 四次挥手断连处理。