很久之前就想写一写关于TCP粘包处理的文章了,无奈一直做WEB开发 没时间研究那个,拖了很久,最近要为一个客户做winform 服务器端,要用到SOCKET就发现了这个问题,这才想起来要解决。

下面用一个网站很多的SOCKET异步通信的例子来做演示:

至于TCP为什么粘包 我这里就不再赘述了,在博客园里一搜很多文章都写的很清楚,我这里就讲一讲我处理TCP粘包的方法,如有不足之处还望提出指正。

====================================

没处理之前的部分核心代码和界面 ,如下:

客户端代码:

 private void buttonConnect_Click(object sender, EventArgs e)
        {
            IPAddress ipAddress = IPAddress.Parse("192.168.1.107");//注意这里 测试的时候要写 服务器的IP , 如果在本机测试 就是自己机器的IP  ,端口注意 如果防火墙弹出提示 放行
            IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);
            client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            connectDone.Reset();
            client.BeginConnect(remoteEP, new AsyncCallback(Connect), client);
            connectDone.WaitOne();
            this.buttonConnect.Enabled = false;
            //
        }
 private void buttonSendBig_Click(object sender, EventArgs e)
        {
            //传统的发送方法
            ///
            ; i < ; i++)
            {
                byte[] bodyBytes = Encoding.UTF8.GetBytes("清明时节雨纷纷,路上行人欲断魂。借问酒家何处有,牧童遥指杏花村。");
                client.BeginSend(bodyBytes, , bodyBytes.Length, , new AsyncCallback(SendCallback), client);
            }
        }

服务器端代码:

  private void buttonStart_Click(object sender, EventArgs e)
        {
            IPHostEntry ipHost = Dns.GetHostEntry(Dns.GetHostName());
            IPAddress ipAddress = ipHost.AddressList[];//获得本机的IP
            IPEndPoint localEndPoint = );
            listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                listener.Bind(localEndPoint);
                listener.Listen();
                this.labelTip.Text = "监听中....";

                threc = new Thread(new ThreadStart(Listen));
                threc.IsBackground = true;
                threc.Start();
                this.buttonStart.Enabled = false;

            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
 private void ReadCallback(IAsyncResult ar)
        {
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;
            try
            {
                int bytesRead = handler.EndReceive(ar);
                )
                {
                    //这里是没拆包的传统方法  如果客户端是传送方式发送的数据包 就用这个办法
                    , bytesRead);//会粘包哦
                    AddMsgToBox(strc);
                    handler.BeginReceive(state.buffer, , StateObject.BufferSize, , new AsyncCallback(ReadCallback), state);
                }
                else
                {
                    AddMsgToBox("客户端退出............");
                }
            }
            catch (Exception ex)
            {
                AddMsgToBox("客户端退出............");
            }
        }

服务器端效果图:

客户端循环发送了20次,理论上服务器应该接收到20条的 ,但是实际上服务器接收到的后面的数据黏在一起!

====================================================

解决方法:

包头+包体=我们要发送的数据

包头的长度是固定的,包头里记录着包体的长度,这样客户端发送的时候每个包都带着包头和包体,服务器端循环接收 找到包头就找到了包体的位置

  [StructLayout(LayoutKind.Sequential)]
        public class PkgHeader
        {
            public int Id;
            public int BodySize;
        }

客户端代码:

 private void buttonSendBig_Click(object sender, EventArgs e)
        {
            //TCP解决粘包的发送方法
            ; i <; i++)
            {
                // 封包体
                byte[] bodyBytes = Encoding.UTF8.GetBytes("清明时节雨纷纷,路上行人欲断魂。借问酒家何处有,牧童遥指杏花村。");

                // 封包头
                PkgHeader header = new PkgHeader();
                header.Id = i;
                header.BodySize = bodyBytes.Length;
                byte[] headerBytes = StructureToByte<PkgHeader>(header);

                // 组合最终发送的封包 (封包头+封包体)
                byte[] sendBytes = GetSendBuffer(headerBytes, bodyBytes);
                client.BeginSend(sendBytes, , sendBytes.Length, , new AsyncCallback(SendCallback), client);
            }
        }

服务器端代码:

private void ReadCallback(IAsyncResult ar)
        {
            StateObject state = (StateObject)ar.AsyncState;
            Socket handler = state.workSocket;
            try
            {
                int bytesRead = handler.EndReceive(ar);
                )
                {

                    //粘包处理
                    //开始拆包 逻辑开始
                    #region TCP 拆包
                    Console.WriteLine("bytesRead=" + bytesRead);
                    ;//接收包位置初始索引
                    ;
                    )
                    {
                        halfLen = byteSub.Length;//不完整包的长度
                        //如果有不完整的包 追加到新的接收包
                        state.buffer = byteSub.Concat(state.buffer).ToArray();
                        byteSub = null;//重置不完整包 防止污染
                    }
                    int total = bytesRead + halfLen;//合并完的总长度 没有不完整包 total就是读取到的长度 bytesRead
                    while (curLen < total)//如果还有 没读取到的字节  就继续读
                    {
                        if (total - curLen < headSize)//如果剩余的字节长度不够一个包头的长度(这时候连包头),存起来留下次合并使用 退出循环 等下次接收
                        {
                            byteSub = new byte[total - curLen];
                            Array.Copy(state.buffer, curLen, byteSub, , total - curLen);
                            break;
                        }
                        byte[] headByte = new byte[headSize];
                        Array.Copy(state.buffer, curLen, headByte, , headSize);//从缓冲区里读取包头的字节
                        PkgHeader header = (PkgHeader)ByteToStructure<PkgHeader>(headByte);//转换成包头的结构体
                        Console.WriteLine("bodySize=" + header.BodySize);
                        if (curLen + headSize + header.BodySize > total)//如果剩余的字节的长度 不够一个包的长度 就存起来 待下次合并包使用
                        {
                            byteSub = new byte[total - curLen];

                            Array.Copy(state.buffer, curLen, byteSub, , byteSub.Length);
                            curLen = total;//已经超出最后一个包的长度 curLen赋最大值 就是当前total
                        }
                        else
                        {
                            //如果够一个包的长度就接收(一次接收的字节可能包含好几个包或者几个包加半个包) 所以用wihle
                            string strc = Encoding.UTF8.GetString(state.buffer, curLen + headSize, header.BodySize);//找到一个包 显示到控件上 while 循环继续找下一个包
                            AddMsgToBox(strc);
                            curLen = curLen + headSize + header.BodySize;//累加已经读取完的字节长度 下次读取时候直接从curLen位置读取下一个包的包头
                        }
                    }
                    #endregion
                    handler.BeginReceive(state.buffer, , StateObject.BufferSize, , new AsyncCallback(ReadCallback), state);
                }
                else
                {
                    AddMsgToBox("客户端退出............");
                }
            }
            catch (Exception ex)
            {
                AddMsgToBox("客户端退出............");
            }
        }

服务器端接收完的效果图

这样接收到的数据都分开了,,,很清晰的`(*∩_∩*)′

DEMO项目下载地址:

点我下载测试项目哦

解决TCP网络传输粘包问题的更多相关文章

  1. 解决TCP网络传输“粘包”问题

    当前在网络传输应用中,广泛采用的是TCP/IP通信协议及其标准的socket应用开发编程接口(API).TCP/IP传输层有两个并列的协议:TCP和UDP.其中TCP(transport contro ...

  2. UNIX网络编程——解决TCP网络传输“粘包”问题

    当前在网络传输应用中,广泛采用的是TCP/IP通信协议及其标准的socket应用开发编程接口(API).TCP/IP传输层有两个并列的协议:TCP和UDP.其中TCP(transport contro ...

  3. Netty处理TCP拆包、粘包

    Netty实践(二):TCP拆包.粘包问题-学海无涯 心境无限-51CTO博客 http://blog.51cto.com/zhangfengzhe/1890577 2017-01-09 21:56: ...

  4. Netty(三) 什么是 TCP 拆、粘包?如何解决?

    前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...

  5. 什么是 TCP 拆、粘包?如何解决(Netty)

    前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...

  6. 网络编程之tcp协议以及粘包问题

    网络编程tcp协议与socket以及单例的补充 一.单例补充 实现单列的几种方式 #方式一:classmethod # class Singleton: # # __instance = None # ...

  7. 第二十八天- tcp下的粘包和解决方案

    1.什么是粘包 写在前面:只有TCP有粘包现象,UDP永远不会粘包 1.TCP下的粘包 因为TCP协议是面向连接.面向流的,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个 ...

  8. 【Python】TCP Socket的粘包和分包的处理

    Reference: http://blog.csdn.net/yannanxiu/article/details/52096465 概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包 ...

  9. TCP 拆、粘包

    Netty(三) 什么是 TCP 拆.粘包?如何解决? 前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 ...

随机推荐

  1. noip2006 2^k进制数

    设r是个2k进制数,并满足以下条件: (1)r至少是个2位的2k进制数. (2)作为2k进制数,除最后一位外,r的每一位严格小于它右边相邻的那一位. (3)将r转换为2进制数q后,则q的总位数不超过w ...

  2. VS2010英文版修改删除、注释快捷键

    VS2010英文版修改删除.注释快捷键 打开快捷键修改面板,然后修改

  3. Delphi下使用Oracle Access控件组下TOraSession控件链接

    Delphi下使用Oracle Access控件组下TOraSession控件链接数据库,使用  orsn1.Options.Direct:=true;  orsn1.Server:=IP:Port: ...

  4. C# 关于时间

    1.2016/7/8 00:10:10 转换成 2016-07-08T 00:10:10 在用VB动态调用WevService的时候,传入的时间格式为2016/7/8 00:10:10,导致调用出错, ...

  5. Linux系统下 解决Qt5工程打不开的方法

    一.问题现象 打开Qt工程的时候,控制台报错:Could not find qmake configuration file default. 二.问题原因 我碰到这种问题的原因是我的Linux系统装 ...

  6. IE浏览器打开f12才正常

    IE浏览器打开f12才正常,最后发现是js中有向控制台打log的语句,程序执行到这些语句就不走了,去掉这些语句就好了!

  7. 用sql从一张表更新数据到另外一张表(多表数据迁移)

    update TBL_1 A, TBL_2 B, TBL_3 Cset a.email=c.email_addrwhere a.user_id=b.user_id and b.un_id=c.un_i ...

  8. 安装数据库出现错误vc_red.msi找不到

    用虚拟光驱安装数据的时候可能会出现,找不到vc_red.msi的问题,通过加载的虚拟光驱目录设置,可能 仍然后问题,比如程序停止运行. 解决方法是:解压iso文件,用解压后的文件安装.然后在解压文件夹 ...

  9. Til the Cows Come Home

    Description Bessie is out in the field and wants to get back to the barn to get as much sleep as pos ...

  10. php pdo预处理语句与存储过程

    很多更成熟的数据库都支持预处理语句的概念.什么是预处理语句?可以把它看作是想要运行的 SQL 的一种编译过的模板,它可以使用变量参数进行定制.预处理语句可以带来两大好处: 1.查询仅需解析(或预处理) ...