很久之前就想写一写关于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. [转]Debug 和 Release 编译方式的区别

    本文主要包含如下内容: 1. Debug 和 Release 编译方式的本质区别 2. 哪些情况下 Release 版会出错 3. 怎样“调试” Release 版的程序 Debug 和 Releas ...

  2. Ubuntu-12.04-server 配置修改静态 IP地址

    前几天在装Ubuntu 12.04 Server版系统的服务器时IP地址写错了,导致服务器不能上网,今天重新修改了一下IP地址,这里做一个总结. 1.配置静态IP地址 sudo vi /etc/net ...

  3. node中的可读流和可写流

    javascript的一个不足之处是不能处理二进制数据,于是node中引入了Buffer类型.这个类型以一个字节(即8位)为单位,给数据分配存储空间.它的使用类似于Array,但是与Array又有不同 ...

  4. stm32 MDK5软件仿真之查看io口输出

    软件MDK5 stm32的pack     打开MDK,添加工程 一.首先找到Project的Options选项,里面的Debug选为Use Simulator,也就是选择软件仿真. 然后再Logic ...

  5. grep 和 wc命令 --- !管道命令!

    Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来.grep全称是Global Regular Expr ession Print,表示全局正则表 ...

  6. python之优雅处理套接字错误

    #!/usr/local/bin/python3.5 #coding:utf-8 import sys import socket import argparse def main(): #setup ...

  7. Boo who

    function boo(bool) { // What is the new fad diet for ghost developers? The Boolean. //return bool; r ...

  8. MSVC 报错 unable to use inline in declaration get error C2054

    晚上用cmake生成了一份lua-cjson的工程文件,msvc6的 编译时报错 后来再stackoverflow找到答案:unable to use inline in declaration ge ...

  9. Java.web-application-development-environments-for-macosx

    Java Web Application开发 1 下载需要的软件 使用的软件并没有采用最新的版本,只是采用了次新版本. 1.1 下载Eclipse的jee版本 eclipse-jee-luna-SR2 ...

  10. 不要轻易delete void*指针,这样会隐藏比较多的错误。

    #include<iostream> using namespace std; class Object{ void* data; const int size; const char i ...