通过系列二 我们已经实现了socket的简单通信 接下来我们测试一下,在时间应用的场景下,我们会快速且大量的传输数据的情况!

    class Program
     {
         static void Main(string[] args)
         {
             TCPListener tcp = new TCPListener();
             TSocketClient client = new TSocketClient();
             ; i < ; i++)
             {
                 client.SendMsg(System.Text.UTF8Encoding.Default.GetBytes("Holle Server!"));
             }
             Console.ReadLine();
         }
     }

我们通过测试代码快速发送10条消息到服务器去,

我们看看运行结果

这样不难看出,我们的客户端发送了10条消息,但是服务器收到的时候变成了两条消息,回复客户端自然就变成两次回复。

这是为什么呢?

我们修改一下程序一秒钟发送一次消息试试

    class Program
     {
         static void Main(string[] args)
         {
             TCPListener tcp = new TCPListener();
             TSocketClient client = new TSocketClient();
             ; i < ; i++)
             {
                 Thread.Sleep();
                 client.SendMsg(System.Text.UTF8Encoding.Default.GetBytes("Holle Server!"));
             }
             Console.ReadLine();
         }
     }

运行看看,

这次对了那么分析分析到底为什么呢?这是socket的底层,做的手脚。因为我设置socket的发送和接受缓冲区//10K的缓冲区空间 private int BufferSize = 10 * 1024; 10k的缓冲区,且socket的底层 发送消息会有一定间隙,虽然这个时间很短,但是我们直接for循环发送的话,时间同意很快,因为socket.send()方法并非真实的发送数据而是把数据压入发送缓冲区。那么我们就明白了为什么会出现上面的情况出现了这样的情况我们要怎么解决呢?时间应用场景不可能1秒钟才一条消息啥。我们知道了导致这个问题的原因是因为消息发送是出现了快速压入很多发送消息到待发送缓冲区里面一起发送导致的。这样情况就是粘包了,那么我们是不是可以考虑给每一个消息加入包标识呢?

接下来我们修改一下发送包的数据代码

创建消息的构造体 TSocketMessage

  /// <summary>
     /// 底层通信消息
     /// </summary>
     public class TSocketMessage : IDisposable
     {
         /// <summary>
         /// 消息ID
         /// </summary>
         public int MsgID;
         /// <summary>
         /// 消息内容
         /// </summary>
         public byte[] MsgBuffer;

         public TSocketMessage(int msgID, byte[] msg)
         {
             this.MsgID = msgID;
             this.MsgBuffer = msg;
         }

         public void Dispose()
         {
             this.Dispose(true);
             GC.SuppressFinalize(this);
         }

         protected virtual void Dispose(bool flag1)
         {
             if (flag1) { this.MsgBuffer = null; }
         }
     }

接下来我们创建消息包的封装和拆分 MarshalEndian

  public class MarshalEndian
     {
         //用于存储剩余未解析的字节数
         );
         //默认是utf8的编码格式
         private UTF8Encoding utf8 = new UTF8Encoding();

         //包头1
         const Int16 t1 = 0x55;
         //包头2
         const Int16 t2 = 0xAA;
         //字节数常量 两个包头4个字节,一个消息id4个字节,封装消息长度 long 8个字节
         const long ConstLenght = 12L;

         public void Dispose()
         {
             this.Dispose(true);
             GC.SuppressFinalize(this);
         }

         protected virtual void Dispose(bool flag1)
         {
             if (flag1)
             {
                 IDisposable disposable2 = this.utf8 as IDisposable;
                 if (disposable2 != null) { disposable2.Dispose(); }
                 IDisposable disposable = this._LBuff as IDisposable;
                 if (disposable != null) { disposable.Dispose(); }
             }
         }

         public byte[] Encode(TSocketMessage msg)
         {
             MemoryStream ms = new MemoryStream();
             BinaryWriter bw = new BinaryWriter(ms, new UTF8Encoding());
             byte[] msgBuffer = msg.MsgBuffer;

             #region 封装包头
             bw.Write((Int16)t1);
             bw.Write((Int16)t2);
             #endregion

             #region 包协议
             if (msgBuffer != null)
             {
                 bw.Write((Int64)(msgBuffer.Length + ));
                 bw.Write(msg.MsgID);
                 bw.Write(msgBuffer);
             }
             ); }
             #endregion

             bw.Close();
             ms.Close();
             bw.Dispose();
             ms.Dispose();
             return ms.ToArray();
         }

         public List<TSocketMessage> GetDcAppMess(byte[] buff, int len)
         {
             //拷贝本次的有效字节
             byte[] _b = new byte[len];
             Array.Copy(buff, , _b, , _b.Length);
             buff = _b;
             )
             {
                 //拷贝之前遗留的字节
                 this._LBuff.AddRange(_b);
                 buff = this._LBuff.ToArray();
                 this._LBuff.Clear();
                 );
             }

             List<TSocketMessage> list = new List<TSocketMessage>();
             MemoryStream ms = new MemoryStream(buff);
             BinaryReader buffers = new BinaryReader(ms, this.utf8);
             try
             {
                 byte[] _buff;
             Label_0073:
                 //判断本次解析的字节是否满足常量字节数
                 if ((buffers.BaseStream.Length - buffers.BaseStream.Position) < ConstLenght)
                 {
                     _buff = new byte[(int)(buffers.BaseStream.Length - buffers.BaseStream.Position)];
                     Array.Copy(buff, (, _buff.Length);
                     this._LBuff.AddRange(_buff);
                     return list;
                 }
                 #region 包头读取
             //循环读取包头
             Label_00983:
                 Int16 tt1 = buffers.ReadInt16();
                 Int16 tt2 = buffers.ReadInt16();
                 if (!(tt1 == t1 && tt2 == t2))
                 {
                     , SeekOrigin.Current);
                     goto Label_00983;
                 }
                 #endregion

                 #region 包协议
                 long offset = buffers.ReadInt64();
                 #endregion

                 #region 包解析
                 //剩余字节数大于本次需要读取的字节数
                 if (offset < (buffers.BaseStream.Length - buffers.BaseStream.Position))
                 {
                     int msgID = buffers.ReadInt32();
                     _buff = ];
                     Array.Copy(buff, (, _buff.Length);
                     list.Add(new TSocketMessage(msgID, _buff));
                     //设置偏移量 然后继续循环读取
                     buffers.BaseStream.Seek(offset, SeekOrigin.Current);
                     goto Label_0073;
                 }
                 else if (offset == (buffers.BaseStream.Length - buffers.BaseStream.Position))
                 {
                     int msgID = buffers.ReadInt32();
                     //剩余字节数刚好等于本次读取的字节数
                     _buff = ];
                     Array.Copy(buff, (, _buff.Length);
                     list.Add(new TSocketMessage(msgID, _buff));
                 }
                 else
                 {
                     //剩余字节数刚好小于本次读取的字节数 存起来,等待接受剩余字节数一起解析
                     _buff = new byte[(int)(buffers.BaseStream.Length - buffers.BaseStream.Position + ConstLenght)];
                     Array.Copy(buff, (, _buff.Length);
                     buff = _buff;
                     this._LBuff.AddRange(_buff);
                 }
                 #endregion

             }
             catch { }
             finally
             {
                 if (buffers != null) { buffers.Dispose(); }
                 buffers.Close();
                 if (buffers != null) { buffers.Dispose(); }
                 ms.Close();
                 if (ms != null) { ms.Dispose(); }
             }
             return list;
         }
     }

接下来我们修改一下 TSocketBase 的 抽象方法

  public abstract void Receive(TSocketMessage msg);

在修改接受消息回调函数

   /// <summary>
         /// 消息解析器
         /// </summary>
         MarshalEndian mersha = new MarshalEndian();

         /// <summary>
         /// 接收消息回调函数
         /// </summary>
         /// <param name="iar"></param>
         private void ReceiveCallback(IAsyncResult iar)
         {
             if (!this.IsDispose)
             {
                 try
                 {
                     //接受消息
                     ReceiveSize = _Socket.EndReceive(iar, out ReceiveError);
                     //检查状态码
                     if (!CheckSocketError(ReceiveError) && SocketError.Success == ReceiveError)
                     {
                         //判断接受的字节数
                         )
                         {
                             byte[] rbuff = new byte[ReceiveSize];
                             Array.Copy(this.Buffers, rbuff, ReceiveSize);
                             var msgs = mersha.GetDcAppMess(rbuff, ReceiveSize);
                             foreach (var msg in msgs)
                             {
                                 this.Receive(msg);
                             }
                             //重置连续收到空字节数
                             ZeroCount = ;
                             //继续开始异步接受消息
                             ReceiveAsync();
                         }
                         else
                         {
                             ZeroCount++;
                             ) { this.Close("错误链接"); }
                         }
                     }
                 }
                 catch (System.Net.Sockets.SocketException) { this.Close("链接已经被关闭"); }
                 catch (System.ObjectDisposedException) { this.Close("链接已经被关闭"); }
             }
         }

这样我们完成了在收到消息后对数据包的解析。

修改一下TSocketClient的 Receive 重写方法

         /// <summary>
         /// 收到消息后
         /// </summary>
         /// <param name="rbuff"></param>
         public override void Receive(TSocketMessage msg)
         {
             Console.WriteLine("Receive ID:" + msg.MsgID + " Msg:" + System.Text.UTF8Encoding.Default.GetString(msg.MsgBuffer));
             if (isServer)
             {
                 this.SendMsg(new TSocketMessage(msg.MsgID, System.Text.UTF8Encoding.Default.GetBytes("Holle Client!")));
             }
         }

修改测试代码如下

     class Program
     {
         static void Main(string[] args)
         {
             TCPListener tcp = new TCPListener();
             TSocketClient client = new TSocketClient();
             ; i < ; i++)
             {
                 Thread.Sleep();
                 client.SendMsg(new TSocketMessage(i, System.Text.UTF8Encoding.Default.GetBytes("Holle Server!")));
             }
             Console.ReadLine();
         }
     }

运行结果

接受成功了,那么我们取消暂停状态,快速发送消息试试

     class Program
     {
         static void Main(string[] args)
         {
             TCPListener tcp = new TCPListener();
             TSocketClient client = new TSocketClient();
             ; i < ; i++)
             {
                 client.SendMsg(new TSocketMessage(i, System.Text.UTF8Encoding.Default.GetBytes("Holle Server!")));
             }
             Console.ReadLine();
         }
     }

看看运行结果

瞬间完成了消息发送,也没有再出现第一次运行的那样~!

这样完美的解决了socket通信 在传输上发送粘包问题

下载程序完整代码

谢谢园友发现的问题,

问题是这样的原本的解包和封包的测试代码不够严谨导致解析包出现错误感谢园友发现问题,并提出问题。

附上最新的代码

 using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Text;
 using System.Threading.Tasks;

 namespace TSocket
 {
     public class MarshalEndian
     {
         //用于存储剩余未解析的字节数
         );
         //默认是utf8的编码格式
         private UTF8Encoding utf8 = new UTF8Encoding();

         //包头1
         const Int16 t1 = 0x55;
         //包头2
         const Int16 t2 = 0xAA;
         //字节数常量 两个包头4个字节,一个消息id4个字节,封装消息长度 int32 4个字节
         ;

         public void Dispose()
         {
             this.Dispose(true);
             GC.SuppressFinalize(this);
         }

         protected virtual void Dispose(bool flag1)
         {
             if (flag1)
             {
                 IDisposable disposable2 = this.utf8 as IDisposable;
                 if (disposable2 != null) { disposable2.Dispose(); }
                 IDisposable disposable = this._LBuff as IDisposable;
                 if (disposable != null) { disposable.Dispose(); }
             }
         }

         public byte[] Encode(TSocketMessage msg)
         {
             MemoryStream ms = new MemoryStream();
             BinaryWriter bw = new BinaryWriter(ms, new UTF8Encoding());
             byte[] msgBuffer = msg.MsgBuffer;

             #region 封装包头
             bw.Write((Int16)t1);
             bw.Write((Int16)t2);
             #endregion

             #region 包协议
             if (msgBuffer != null)
             {
                 bw.Write((Int32)(msgBuffer.Length + ));
                 bw.Write(msg.MsgID);
                 bw.Write(msgBuffer);
             }
             ); }
             #endregion

             bw.Close();
             ms.Close();
             bw.Dispose();
             ms.Dispose();
             return ms.ToArray();
         }

         public List<TSocketMessage> GetDcAppMess(byte[] buff, int len)
         {
             //拷贝本次的有效字节
             byte[] _b = new byte[len];
             Array.Copy(buff, , _b, , _b.Length);
             buff = _b;
             )
             {
                 //拷贝之前遗留的字节
                 this._LBuff.AddRange(_b);
                 buff = this._LBuff.ToArray();
                 this._LBuff.Clear();
                 );
             }

             List<TSocketMessage> list = new List<TSocketMessage>();
             MemoryStream ms = new MemoryStream(buff);
             BinaryReader buffers = new BinaryReader(ms, this.utf8);
             try
             {
                 byte[] _buff;
             Label_00983:

                 #region 包头读取
                 //循环读取包头
                 //判断本次解析的字节是否满足常量字节数
                 if ((buffers.BaseStream.Length - buffers.BaseStream.Position) < ConstLenght)
                 {
                     _buff = buffers.ReadBytes((int)(buffers.BaseStream.Length - buffers.BaseStream.Position));
                     this._LBuff.AddRange(_buff);
                     return list;
                 }
                 Int16 tt1 = buffers.ReadInt16();
                 Int16 tt2 = buffers.ReadInt16();
                 if (!(tt1 == t1 && tt2 == t2))
                 {
                     , SeekOrigin.Current);
                     goto Label_00983;
                 }
                 #endregion

                 #region 包协议
                 int offset = buffers.ReadInt32();
                 #endregion

                 #region 包解析
                 //剩余字节数大于本次需要读取的字节数
                 if (offset <= (buffers.BaseStream.Length - buffers.BaseStream.Position))
                 {
                     int msgID = buffers.ReadInt32();
                     _buff = buffers.ReadBytes(offset - );
                     list.Add(new TSocketMessage(msgID, _buff));
                     )
                     {
                         goto Label_00983;
                     }
                 }
                 else
                 {
                     //剩余字节数刚好小于本次读取的字节数 存起来,等待接受剩余字节数一起解析
                     _buff = buffers.ReadBytes((int)(buffers.BaseStream.Length - buffers.BaseStream.Position + ConstLenght));
                     this._LBuff.AddRange(_buff);
                 }
                 #endregion
             }
             catch (Exception ex) { Console.WriteLine(ex); }
             finally
             {
                 if (buffers != null) { buffers.Dispose(); }
                 buffers.Close();
                 if (buffers != null) { buffers.Dispose(); }
                 ms.Close();
                 if (ms != null) { ms.Dispose(); }
             }
             return list;
         }
     }
 }

C# Socket系列三 socket通信的封包和拆包的更多相关文章

  1. socket系列之socket服务端与客户端如何通信

    上面已经分别介绍了ServerSocket跟Socket的工作步骤,并且从应用层往系统底层剖析其运作原理,我们清楚了他们各自的一块,现在我们将把他们结合起来,看看他们是如何通信的,并详细讨论一下他们之 ...

  2. java学习小笔记(三.socket通信)【转】

    三,socket通信1.http://blog.csdn.net/kongxx/article/details/7288896这个人写的关于socket通信不错,循序渐进式的讲解,用代码示例说明,运用 ...

  3. 网络编程 TCP协议:三次握手,四次回收,反馈机制 socket套接字通信 粘包问题与解决方法

    TCP协议:传输协议,基于端口工作 三次握手,四次挥手 TCP协议建立双向通道. 三次握手, 建连接: 1:客户端向服务端发送建立连接的请求 2:服务端返回收到请求的信息给客户端,并且发送往客户端建立 ...

  4. C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

    介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...

  5. 进程间通信系列 之 socket套接字实例

    进程间通信系列 之 概述与对比   http://blog.csdn.net/younger_china/article/details/15808685  进程间通信系列 之 共享内存及其实例   ...

  6. 进程间通信系列 之 socket套接字及其实例

    进程间通信系列 之 概述与对比   http://blog.csdn.net/younger_china/article/details/15808685  进程间通信系列 之 共享内存及其实例   ...

  7. python socket+tcp三次握手四次撒手学习+wireshark抓包

    Python代码: server: #!/usr/bin/python # -*- coding: UTF-8 -*- # 文件名:server.py import socket # 导入 socke ...

  8. UE4 Socket多线程非阻塞通信

    转自:https://blog.csdn.net/lunweiwangxi3/article/details/50468593 ue4自带的Fsocket用起来依旧不是那么的顺手,感觉超出了我的理解范 ...

  9. Python socket套接字通信

    一.什么是socket? socket是一个模块, 又称套接字,用来封装 互联网协议(应用层以下的层). 二.为什么要有socket? socket可以实现互联网协议 应用层以下的层 的工作,提高开发 ...

随机推荐

  1. jq判断 复选框是否被选中 亲测可用

    var shortName = $('#shortName').is(':checked')?1:0;

  2. .net与mono的那些事

    米格尔·德伊卡萨在.NET的文档于2000年10月发布时就马上对.NET产生了兴趣.在查看字节码解释器后,他发现对于元数据(metadata)没有相应的说明文档.2001年2月,德伊卡萨在.NET邮件 ...

  3. WPF快速入门系列(7)——深入解析WPF模板

    一.引言 模板从字面意思理解是“具有一定规格的样板".在现实生活中,砖块都是方方正正的,那是因为制作砖块的模板是方方正正的,如果我们使模板为圆形的话,则制作出来的砖块就是圆形的,此时我们并不 ...

  4. 旺信UWP公测邀请

    各位园主好,今天已将旺信Win10版提交到商店Beta测试. 哪位朋友需要邀请码的,请在评论中回复,我给你私信. 数量有限,共10枚. 2016/3/10 14:55 更新 10枚邀请码已发给前10位 ...

  5. CSS Font知识整理总结

    1.什么是字体 字体是文字的外在形式,就是文字的风格,是文字的外衣.比如行书.楷书.草书,都是一种字体.同样一个字每个人写起来都会有差异,可以说每个人都有一套潜在的字体库.对于web页面来说,字体就是 ...

  6. [.net 面向对象程序设计进阶] (2) 正则表达式 (一) 快速入门

    [.net 面向对象程序设计进阶] (2) 正则表达式 (一) 快速入门 1. 什么是正则表达式? 1.1 正则表达式概念 正则表达式,又称正则表示法,英文名:Regular Expression(简 ...

  7. java提高篇(十四)-----字符串

          可以证明,字符串操作是计算机程序设计中最常见的行为. 一.String 首先我们要明确,String并不是基本数据类型,而是一个对象,并且是不可变的对象.查看源码就会发现String类为f ...

  8. 浅谈Excel开发:四 Excel 自定义函数

    我们知道,Excel中有很多内置的函数,比如求和,求平均,字符串操作函数,金融函数等等.在有些时候,结合业务要求,这些函数可能不能满足我们的需求,比如我想要一个函数能够从WebService上获取某只 ...

  9. ubuntu:solve the problem of 'E:Problem with MergeList /var/lib/apt/lists/'

    just run this command: sudo rm /var/lib/apt/lists/* -vfR it will remove all the software package wit ...

  10. 说说设计模式~观察者模式(Observer)

    返回目录 观察者模式,也叫发布/订阅模式(publish/subscribe),监视器模式等.在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知.这通常透过呼 ...