很久之前就想写一写关于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. 调试CS5343总结报告

    一周前接到新任务,调试CS5343,百度一下,CS5343是一款音频采集的AD芯片,CS5343驱动代码是现成的,我的工作是提高芯片的采样速率,看了一边芯片的Datesheet,辛好东西不是很多. 概 ...

  2. git常用操作

    批量删除以bran开头的本地分支 git branch |grep 'bran'|xargs git branch -d

  3. DropDownList怎样动态绑定数据库中的某一条数据

    用Ajax动态绑定数据库的数据:点击后台查看代码,编写代码如下 if (!IsPostBack) { using (SnailTechDataContext con = new SnailTechDa ...

  4. arm64 boot

    http://www.wowotech.net/linux_kenrel/arm64_initialize_1.html

  5. DIOCP网络通讯流程

    DIOCP 运作核心探密   原文连接: http://blog.qdac.cc/?p=2362 原作者: BB 天地弦的DIOCP早已经广为人知了,有很多的同学都用上了它,甚至各种变异.修改版本也出 ...

  6. Linq查询数据集取得排序后的序列号(行号)

    今天群里有同学问如何用linq取结果集的行号,查了一下资料,发现linq有很简单的方法可以实现,花了几分钟写了一个测试用例,现记录下来,以备参考: /// <summary> /// 测试 ...

  7. C_C++圣战(摘录)

    我的回忆和有趣的故事 --- C/C++圣战篇 李维 (声明以下的这篇文章内容是我个人的回忆以及看法,没有任何特别的偏见,许多的事情是根据我的记忆以及从许多人的诉说中得知的,也许内容不是百分之百的正确 ...

  8. android笔记:DatePickerDialog日期设置对话框

    在开发中,可以通过DatePickerDialog来设置日期,TimePickerDialog来设置时间. 实例化DatePickerDialog对象之后,再调用show方法就可以显示对话框了. 具体 ...

  9. sql语句 关于日期时间、类型转换的东西

    (一) 1, select update_date, CONVERT(VARCHAR(30),update_date,111) jj ,CONVERT(VARCHAR(30),update_date, ...

  10. IOS一些好的用户体验设置

    1,下载图片时,如果 用户操作UI,那么就停止子线程,用户停止操作子线程时,开启子线程继续下载. SDWebImage  :专门下载图片. 2,网络请求时.本地要进行一些验证,以减少服务器的压力.