很久之前就想写一写关于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. 使用timer控件控制进度条

    using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...

  2. 面向服务体系架构(SOA)和数据仓库(DW)的思考基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台

    面向服务体系架构(SOA)和数据仓库(DW)的思考 基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台 当前业界对面向服务体系架构(SOA)和数据仓库(Data Warehouse, ...

  3. java文件末尾追加内容的两种方式

    java 开发中,偶尔会遇到在文件末尾对文件内容进行追加,实际上有多种方式可以实现,简单介绍两种: 一种是通过RandomAccessFile类实现,另一种是通过FileWriter类来实现. 实现方 ...

  4. singleton(单件)-对象创建型模式

    1.意图 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 2.动机 对一些类来说,只有一个实例是很重要的.让类自身负责保存它唯一的实例,这个类可以保证没有其他实例可以被创建(通过截取创建新对象 ...

  5. SPI总线的特点、工作方式及常见错误解答

    1.SPI总线简介 SPI(serial peripheral interface,串行外围设备接口)总线技术是Motorola公司推出的一种同步串行接口.它用于CPU与各种外围器件进行全双工.同步串 ...

  6. XML Xpath学习

    Xpath是一门在xml文档中查找信息的语言. Xpath可用来在xml文档中对元素和属性进行遍历. <1>路径表达式1: 斜杠(/)作为路径内部的分隔符 同一个路径有绝对路径和相对路径两 ...

  7. ELK日志管理之——elasticsearch部署

    1.配置官方yum源 [root@localhost ~]# rpm --import http://packages.elasticsearch.org/GPG-KEY-elasticsearch ...

  8. python学习笔记-Day6(3)

    代码书写原则: 1)不能重复写代码 2)写的代码要经常变更 编程模式概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数 ...

  9. Cannot fetch index base URL https://pypi.python.org/simple/

    这个就是相源的问题,正常安装你的根目录下会有这个pip.log文件,如下 root@liu:~# ll .pip/ total 16 drwxr-xr-x 2 root root 4096 Sep 1 ...

  10. 用PHP将Unicode 转化为UTF-8

    function unescape($str) { $str = rawurldecode($str); preg_match_all("/(?:%u.{4})|&#x.{4};|& ...