解决TCP网络传输粘包问题
很久之前就想写一写关于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网络传输粘包问题的更多相关文章
- 解决TCP网络传输“粘包”问题
当前在网络传输应用中,广泛采用的是TCP/IP通信协议及其标准的socket应用开发编程接口(API).TCP/IP传输层有两个并列的协议:TCP和UDP.其中TCP(transport contro ...
- UNIX网络编程——解决TCP网络传输“粘包”问题
当前在网络传输应用中,广泛采用的是TCP/IP通信协议及其标准的socket应用开发编程接口(API).TCP/IP传输层有两个并列的协议:TCP和UDP.其中TCP(transport contro ...
- Netty处理TCP拆包、粘包
Netty实践(二):TCP拆包.粘包问题-学海无涯 心境无限-51CTO博客 http://blog.51cto.com/zhangfengzhe/1890577 2017-01-09 21:56: ...
- Netty(三) 什么是 TCP 拆、粘包?如何解决?
前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...
- 什么是 TCP 拆、粘包?如何解决(Netty)
前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 HTTP ,而是利用 Netty 自定义的协议. 有个 ...
- 网络编程之tcp协议以及粘包问题
网络编程tcp协议与socket以及单例的补充 一.单例补充 实现单列的几种方式 #方式一:classmethod # class Singleton: # # __instance = None # ...
- 第二十八天- tcp下的粘包和解决方案
1.什么是粘包 写在前面:只有TCP有粘包现象,UDP永远不会粘包 1.TCP下的粘包 因为TCP协议是面向连接.面向流的,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个 ...
- 【Python】TCP Socket的粘包和分包的处理
Reference: http://blog.csdn.net/yannanxiu/article/details/52096465 概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包 ...
- TCP 拆、粘包
Netty(三) 什么是 TCP 拆.粘包?如何解决? 前言 记得前段时间我们生产上的一个网关出现了故障. 这个网关逻辑非常简单,就是接收客户端的请求然后解析报文最后发送短信. 但这个请求并不是常见的 ...
随机推荐
- 使用timer控件控制进度条
using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using Sy ...
- 面向服务体系架构(SOA)和数据仓库(DW)的思考基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台
面向服务体系架构(SOA)和数据仓库(DW)的思考 基于 IBM 产品体系搭建基于 SOA 和 DW 的企业基础架构平台 当前业界对面向服务体系架构(SOA)和数据仓库(Data Warehouse, ...
- java文件末尾追加内容的两种方式
java 开发中,偶尔会遇到在文件末尾对文件内容进行追加,实际上有多种方式可以实现,简单介绍两种: 一种是通过RandomAccessFile类实现,另一种是通过FileWriter类来实现. 实现方 ...
- singleton(单件)-对象创建型模式
1.意图 保证一个类仅有一个实例,并提供一个访问它的全局访问点. 2.动机 对一些类来说,只有一个实例是很重要的.让类自身负责保存它唯一的实例,这个类可以保证没有其他实例可以被创建(通过截取创建新对象 ...
- SPI总线的特点、工作方式及常见错误解答
1.SPI总线简介 SPI(serial peripheral interface,串行外围设备接口)总线技术是Motorola公司推出的一种同步串行接口.它用于CPU与各种外围器件进行全双工.同步串 ...
- XML Xpath学习
Xpath是一门在xml文档中查找信息的语言. Xpath可用来在xml文档中对元素和属性进行遍历. <1>路径表达式1: 斜杠(/)作为路径内部的分隔符 同一个路径有绝对路径和相对路径两 ...
- ELK日志管理之——elasticsearch部署
1.配置官方yum源 [root@localhost ~]# rpm --import http://packages.elasticsearch.org/GPG-KEY-elasticsearch ...
- python学习笔记-Day6(3)
代码书写原则: 1)不能重复写代码 2)写的代码要经常变更 编程模式概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数 ...
- 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 ...
- 用PHP将Unicode 转化为UTF-8
function unescape($str) { $str = rawurldecode($str); preg_match_all("/(?:%u.{4})|&#x.{4};|& ...