很久之前就想写一写关于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. Docker上运行dotnet core

    下载microsoft/dotnet镜像 运行命令: docker pull microsoft/dotnet 如果没有使用阿里镜像加速的,参照这篇先配置好再跑上面命令: http://www.cnb ...

  2. (转)iOS sqlite :truncate/delete/drop区分

    转自:http://blog.sina.com.cn/s/blog_6755689f0101fofb.html 相同点: 1.truncate和不带where子句的delete.以及drop都会删除表 ...

  3. 如何扫描二维码下载APK

    将apk文件放到网站上,即用户可以通过www.xxx.com.cn/abc.apk直接下载 再www.xxx.com.cn/abc.apk这个字符串做成二维码就可以了. 问题: 直接放到网站后,输入下 ...

  4. (引用 )自动化测试报告HTMLtestrunner

    1>下载HTMLTestRunner.py文件,地址为: http://tungwaiyip.info/software/HTMLTestRunner.html   Windows平台: 将下载 ...

  5. deep learning

    今天跑一个模型,程序都没变,就配置文件变了.但是总是很快就显示loss为nan. 检查配置文件还是不行,把其中loss改为0还是不行.最后搁置了一下,再回头对比一下电脑上的和服务器上的,发现一个配置文 ...

  6. 转贴 IT外企那点儿事完整版

    转贴 IT外企那点儿事完整版 第一章:外企也就那么回儿事(http://www.cnblogs.com/forfuture1978/archive/2010/04/30/1725341.html) 1 ...

  7. Python环境下NIPIR(ICTCLAS2014)中文分词系统使用攻略

    一.安装 官方链接:http://pynlpir.readthedocs.org/en/latest/installation.html 官方网页中介绍了几种安装方法,大家根据个人需要,自行参考!我采 ...

  8. Revenge of Nim hdu 4994 (博弈)

    http://acm.split.hdu.edu.cn/showproblem.php?pid=4994 题意:现在有两个人在取石子,共有n堆石子,每堆石子取完后才可以取下一堆石子,最后一个取石子的人 ...

  9. main与对象初始化 in C++

    没有学过代码编译的原理,以前也没有兴趣去学编译器的相关原理,但是近期通过阅读google开源项目gtest,对我稍有触动. 代码: main test示例 TEST宏定义 #define TEST(t ...

  10. centos 添加 composer

    下载安装包 curl -sS https://getcomposer.org/installer | php 把 composer 把复制到 /usr/bin/composer mv composer ...