c#软件开发学习笔记--TCP 粘包拆包、SqlSugar 框架
发布时间:2026/8/19 12:41:08 作者:尧图编辑部 阅读量:1,286

学习总结TCP 粘包拆包 与 SqlSugar 框架一、TCP 粘包与拆包1.1 问题背景TCP 是面向字节流的协议发送方写入的数据在接收方看来是一条连续不断的字节流没有边界。因此会出现以下三种现象现象说明粘包多次发送的消息被合并成一次接收例如两条短消息挤在一个数据包中到达拆包一条消息被拆成多次接收例如大消息被网络层切成多个数据包混合既有粘包又有拆包同时发生注意粘包拆包不是 TCP 的 Bug而是字节流协议的固有特性。UDP 是面向报文的协议不存在此问题。1.2 常见解包方案消息定长每条消息固定长度不足补齐。特殊分隔符例如以\r\n结尾FTP、HTTP 头部采用此方式。头部长度字段本案例采用在消息体前加一个固定长度的头部记录消息体的字节长度。1.3 协议设计本案例本案例采用“4 字节头 变长消息体”的方案------------------------------- | Header | Body | | 4 bytes | N bytes | | (body长度) | (UTF8 文本) | -------------------------------头部4 字节整数存储消息体的字节长度字节序网络字节序大端使用IPAddress.HostToNetworkOrder/NetworkToHostOrder转换消息体UTF-8 编码的字符串1.4 关键代码1.4.1 编码消息 → 字节流/// summary/// 将字符串编码为「4字节长度头 消息体」的字节包/// /summaryprivatebyte[]EncodePackage(stringmsg){byte[]dataEncoding.UTF8.GetBytes(msg);// 将消息体长度转为网络字节序大端保证跨平台一致byte[]headLengthBitConverter.GetBytes(IPAddress.HostToNetworkOrder(data.Length));byte[]packagenewbyte[4data.Length];Array.Copy(headLength,package,headLength.Length);// 头部Array.Copy(data,0,package,4,data.Length);// 消息体returnpackage;}1.4.2 解码从字节流中循环抽取完整包/// summary/// 从缓冲区 bsList 中循环解析出完整消息/// 粘包一次能解析出多条拆包长度不足时跳出等待下次数据/// /summaryprivatevoidTryParsePackage(){while(bsList.Count4)// 至少要有 4 字节头部{byte[]headBytesbsList.GetRange(0,4).ToArray();intbodyLengthIPAddress.NetworkToHostOrder(BitConverter.ToInt32(headBytes,0));inttotalLength4bodyLength;if(bsList.CounttotalLength)break;// 拆包包不完整等下一次byte[]bodyDatabsList.GetRange(4,bodyLength).ToArray();stringmsgEncoding.UTF8.GetString(bodyData);AppendLog(收到消息msg);bsList.RemoveRange(0,totalLength);// 移除已解析部分继续解析下一个}}1.4.3 接收数据 缓冲/// summary/// 异步循环读取网络流累积到 bsList 后尝试解析/// /summaryprivatevoidReceiveData(TcpClientclient){ctsnewCancellationTokenSource();Task.Run(async(){byte[]buffernewbyte[1024];while(!cts.IsCancellationRequested){NetworkStreamstreamclient.GetStream();intlenawaitstream.ReadAsync(buffer,cts.Token);if(len0){AppendLog(客户端断开连接);break;}byte[]realDatanewbyte[len];Array.Copy(buffer,realData,len);bsList.AddRange(realData);// 关键累积到缓冲区TryParsePackage();}},cts.Token);}1.5 流程示意发送端 msg ──EncodePackage── [4字节头][body] ── stream.Write 接收端 stream.Read ── bsList.AddRange ── TryParsePackage (while循环) │ ┌─────────────┴─────────────┐ │ bsList.Count 4 ? │ │ 取出头部 → bodyLength │ │ 总长 4bodyLength │ │ 不足 → break (拆包等待) │ │ 足够 → 取body → 处理 → 移除 │ └───────────────────────────┘1.6 要点小结✅ 用Listbyte作为接收缓冲区累积所有到达字节✅ 用while循环解析一次可拆出多个包解决粘包✅ 包不完整时break等待下次数据解决拆包✅ 头部使用网络字节序保证收发双方字节序一致✅ 解析完一个包后必须RemoveRange移除已处理部分二、SqlSugar 第三方 ORM 框架2.1 框架简介SqlSugar是一款老牌国产 .NET ORM 框架由果糖大数据科技团队维护开箱即用、易于上手。2.2 两种使用模式模式类特点原生模式SqlSugarClient高性能每次 new 新对象禁止单例单例模式SqlSugarScope对新手友好解决大部分线程安全问题必须单例2.3 核心配置本案例采用原生模式/// summary/// 窗体加载时初始化 SqlSugarClient/// /summaryprivatevoidForm1_Load(objectsender,EventArgse){dbnewSqlSugarClient(newConnectionConfig(){ConnectionStringserver localhost,64345; database sugarDemo.db; uid sa; pwd 123123;EncryptTrue;TrustServerCertificateTrue,DbTypeDbType.SqlServer,IsAutoCloseConnectiontrue,// 自动关闭连接省去手动 using},db{// AOP执行 SQL 时打印原生 SQL方便调试db.Aop.OnLogExecuting(sql,pars){Console.WriteLine(UtilMethods.GetNativeSql(sql,pars));};});}2.4 实体定义[SugarTable]// 标记为数据库表publicclassStudent{[SugarColumn(IsPrimaryKeytrue,IsIdentitytrue)]// 主键 自增publicintId{get;set;}publicstringName{get;set;}publicint?Age{get;set;}// 可空类型对应数据库可空字段}2.5 数据库与表初始化// 创建数据库若不存在db.DbMaintenance.CreateDatabase();// CodeFirst根据实体类自动生成/同步表结构db.CodeFirst.InitTables(typeof(Student));2.6 核心 APICRUD操作API示例增db.InsertableT(entity).ExecuteCommand()db.Insertable(list).ExecuteCommand()删db.DeleteableT(entity).ExecuteCommand()db.DeleteableStudent(stu).ExecuteCommand()改db.UpdateableT(entity).ExecuteCommand()db.UpdateableStudent(stu).ExecuteCommand()查db.QueryableT()...ToList()db.QueryableStudent().Single(s s.Id id)2.6.1 批量插入ListStudentlistnewListStudent(){newStudent(){Name张三1,Age100},newStudent(){Name张三2,Age200},newStudent(){Name张三3,Age300},};introwdb.Insertable(list).ExecuteCommand();2.6.2 条件查询 动态拼接使用ExpressionableT动态拼接 where 条件避免写大量 if-else 分支 SQL。/// summary/// 根据界面输入条件动态拼接查询条件并分页绑定 DataGridView/// /summaryprivatevoidBindView(){if(dbnull)return;varexpressExpressionable.CreateStudent();// AndIF仅当条件成立时追加 AND 过滤express.AndIF(!string.IsNullOrWhiteSpace(textBox1.Text),ss.Name.Contains(textBox1.Text));express.AndIF(!string.IsNullOrWhiteSpace(textBox2.Text),ss.Ageint.Parse(textBox2.Text));express.AndIF(!string.IsNullOrWhiteSpace(textBox3.Text),ss.Ageint.Parse(textBox3.Text));varlistdb.QueryableStudent().Where(express.ToExpression()).ToPageList(pageNumber,pageSize,reftotalCount,reftotalPages).ToList();dataGridView1.DataSourcelist;label2.Text${pageNumber}/{totalPages};}2.6.3 修改与删除按主键定位// 修改先查后改intidint.Parse(dataGridView1.SelectedRows[0].Cells[Id].Value!.ToString()!);Studentstudb.QueryableStudent().Single(ss.Idid);stu.Name三生三世;stu.Age100;db.UpdateableStudent(stu).ExecuteCommand();// 删除Studentstudb.QueryableStudent().Single(ss.Idid);db.DeleteableStudent(stu).ExecuteCommand();2.7 分页查询要点ToPageList(pageNumber, pageSize, ref totalCount, ref totalPages)一次返回当前页数据 总数 总页数翻页只需修改pageNumber后重新调用BindView()每页条数变更时应重置pageNumber 1避免越界2.8 要点小结✅IsAutoCloseConnection true让连接管理自动化避免资源泄漏✅Aop.OnLogExecuting是调试 SQL 的利器✅ExpressionableT适合动态条件查询场景✅CodeFirst.InitTables实现代码先行实体变更直接同步表结构✅ CRUD 全部链式调用统一以ExecuteCommand()增删改或ToList()/Single()查收尾三、关联知识点ADO.NET 五大对象SqlConnection、SqlCommand、SqlDataAdapter、SqlDataReader、DataSetSqlSugar 底层即基于此封装。IOC / DISqlSugarClient 推荐使用 Scope 或瞬发注入禁止单例。网络字节序TCP 自定义协议应统一使用大端序HostToNetworkOrder/NetworkToHostOrder。