tcp服务端和客户端建立连接后会长时间维持这个连接,用于互相传递数据,tcp是以流的方式传输数据的,就像一个水管里的水一样,从一头不断的流向另一头。

理想情况下,发送的数据包都是独立的,

现实要复杂一些,发送方和接收方都有各自的缓冲区。

发送缓冲区:应用不断的把数据发送到缓冲区,系统不断的从缓冲区取数据发送到接收端。

接收缓冲区:系统把接收到的数据放入缓冲区,应用不断的从缓冲区获取数据。

当发送方快速的发送多个数据包时,每个数据包都小于缓冲区,tcp会将多次写入的数据放入缓冲区,一次发送出去,服务器在接收到数据流无法区分哪部分数据包独立的,这样产生了粘包。

或者接收方因为各种原因没有从缓冲区里读取数据,缓冲区的数据会积压,等再取出数据时,也是无法区分哪部分数据包独立的,一样会产生粘包。

发送方的数据包大于缓存区了,其中有一部分数据会在下一次发送,接收端一次接收到时的数据不是完整的数据,就会出现半包的情况。



我们可以还原一下粘包和半包,写一个测试代码

服务端

func main() {
l, err := net.Listen("tcp", ":8899")
if err != nil {
panic(err)
}
fmt.Println("listen to 8899")
for {
conn, err := l.Accept()
if err != nil {
panic(err)
} else {
go handleConn(conn)
}
}
} func handleConn(conn net.Conn) {
defer conn.Close()
var buf [1024]byte
for {
n, err := conn.Read(buf[:])
if err != nil {
break
} else {
fmt.Printf("recv: %s \n", string(buf[0:n]))
}
}
}

客户端

func main() {
data := []byte("~测试数据:一二三四五~")
conn, err := net.Dial("tcp", ":8899")
if err != nil {
panic(err)
}
for i := 0; i < 2000; i++ {
if _, err = conn.Write(data); err != nil {
fmt.Printf("write failed , err : %v\n", err)
break
}
}
}

查看一下输出

recv: ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~ ~测试数据:一二三四五~
recv: ~测试数据:一�
recv: ��三四五~ ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~ ~测试数据:一二三四五~ ~测试数据:一二三四五~ ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~

正常情况下输出是recv: ~测试数据:一二三四五~,发生粘包的时候会输出多个数据包,当有半包的情况下输出的是乱码数据,再下一次会把剩下的半包数据也输出。

要解决也简单的就想办法确定数据的边界,常见的处理方式:

  • 固定长度: 比如规定所有的数据包长度为100byte,如果不够则补充至100长度。优点就是实现很简单,缺点就是空间有极大的浪费,如果传递的消息中大部分都比较短,这样就会有很多空间是浪费的,同样浪费的还有流量。
  • 分隔符:用分隔符来确定数据的边界,这样做比较简单也不浪费空间,但数据包内就不能包含相应的分隔符,如果有会造成错误的解析。
  • 数据头:通过数据头部来解析数据包长度,比如用4个字节来当数据头,保存每个实数据包的长度。

个人更推荐数据头方式来确定数据边界,在发送和接收数据时做好规定,每个数据包是不定长的,比如4字节的包头+真实的数据可以根据自己的业务进行扩展,比如上更多的包头或者包尾,加上数据校验等。

我修改一下上面的代码:

客户端

	data := []byte("~测试数据:一二三四五~")
conn, err := net.Dial("tcp", ":8899")
if err != nil {
panic(err)
}
for i := 0; i < 2000; i++ {
var total int64 = -1
var buf [4]byte
bufs := buf[:]
binary.BigEndian.PutUint32(bufs, uint32(len(data)))
n, err := conn.Write(bufs)
total += int64(n)
n, err = conn.Write(data)
total += int64(n)
if err != nil {
fmt.Printf("write failed , err : %v\n", err)
break
}
}

服务端

func main() {
l, err := net.Listen("tcp", ":8899")
if err != nil {
panic(err)
}
fmt.Println("listen to 8899")
for {
conn, err := l.Accept()
if err != nil {
panic(err)
} else {
go handleConn(conn)
}
}
}
func handleConn(conn net.Conn) {
defer conn.Close()
for {
var msgSize int32
err := binary.Read(conn, binary.BigEndian, &msgSize)
if err != nil {
break
}
buf := make([]byte, msgSize)
_, err = io.ReadFull(conn, buf)
if err != nil {
break
}
fmt.Printf("recv: %s \n", string(buf))
}
}

执行再看一下输出,没有粘包或者半包的情况

recv: ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~
recv: ~测试数据:一二三四五~

也可以像第一个例子一样用一个指定大小的buf var buf [1024]byte,每次从conn里取出指定大小的数据,然后进行数据解析,如果发现有半包的情况,就再读取一次,加上上次未解析的数据,再次重新解析。

详说tcp粘包和半包的更多相关文章

  1. 关于TCP封包、粘包、半包

    关于Tcp封包 很多朋友已经对此作了不少研究,也花费不少心血编写了实现代码和blog文档.当然也充斥着一些各式的评论,自己看了一下,总结一些心得. 首先我们学习一下这些朋友的心得,他们是: http: ...

  2. socket编程 TCP 粘包和半包 的问题及解决办法

    一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系. 粘包 为x.5个包 半包 为0.5个包 由于网络原因 一次 ...

  3. TCP的粘包、半包和Netty的处理

    参考文献:极客时间傅健老师的<Netty源码剖析与实战>Talk is cheap.show me the code! 什么是粘包和半包 在客户端发送数据时,实际是把数据写入到了TCP发送 ...

  4. Netty - 粘包和半包(上)

    在网络传输中,粘包和半包应该是最常出现的问题,作为 Java 中最常使用的 NIO 网络框架 Netty,它又是如何解决的呢?今天就让我们来看看. 定义 TCP 传输中,客户端发送数据,实际是把数据写 ...

  5. C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

    介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...

  6. Netty - 粘包和半包(下)

    上一篇介绍了粘包和半包及其通用的解决方案,今天重点来看一下 Netty 是如何实现封装成帧(Framing)方案的. 解码核心流程 之前介绍过三种解码器FixedLengthFrameDecoder. ...

  7. TCP的组包、半包、粘包与分包

    一.概念 1)组包.简单的说就是tcp协议把过大的数据包分成了几个小的包传输,接收方要把同一组的数据包重新组合成一个完整的数据包. 2)半包.指接受方没有接受到一个完整的包,只接受了部分,这种情况主要 ...

  8. c# socket 解决粘包,半包

    处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断 ...

  9. TCP粘包和半包的处理方法

    先把处理的方法的代码放这里: 解析数据帧的代码: bool CSocket::findData(byte* buff, int& len) { for (int i = 0; i <= ...

随机推荐

  1. 关于mantisBT2.22安装插件Inline column configuration 2.0.0时提示缺少依赖jQuery UI Library 1.8

    1.首先直接下载最新的那个1.12即可 https://github.com/mantisbt-plugins/jQuery-UI/releases 2.上传到mantis的插件目录下:……manti ...

  2. 1700人点反对的LeetCode问题,是因为太难了吗?

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是LeetCode专题的第40篇文章,我们一起来看的是LeetCode中的71题Simplify Path,中文名是简化路径. 这题的难 ...

  3. 上位机开发之三菱FX3U以太网通信实践

    上次跟大家介绍了一下上位机与三菱Q系列PLC通信的案例,大家可以通过点击这篇文章:上位机开发之三菱Q系列PLC通信实践(←戳这里) 今天以三菱FX3U PLC为例,跟大家介绍一下,如何实现上位机与其之 ...

  4. [COCOS2DX-LUA]0-005.cocos2dx中关于全面屏和折叠屏的适配的一些见解

    1.随着科技的发展,我们可以看到从iphoneX的刘海屏开始,引发了各种全面屏和异形屏的出现.这是科技的进步,但是对于各大的应用厂商来说,苦不堪言. 2.当然 ,吐槽归吐槽,我们还是要理智的去对待这个 ...

  5. RabbitMQ镜像集群搭建

    RabbitMQ 官网 https://www.rabbitmq.com/ 小编使用的系统环境是CentOS7.4 系统 IP hostname CentOS7.4 1.1.1.1 hostname0 ...

  6. 这些Java8官方挖过的坑,你踩过几个?

    导读:系统启动异常日志竟然被JDK吞噬无法定位?同样的加密方法,竟然出现部分数据解密失败?往List里面添加数据竟然提示不支持?日期明明间隔1年却输出1天,难不成这是天上人间?1582年神秘消失的10 ...

  7. 2003 can't connect to mysql server

    在电脑中打开 计算机管理 点击 服务与应用程序 点击 服务 右侧找到 mysql 右键启动

  8. Java实现 蓝桥杯 传纸条

    题目描述 小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题.一次素质拓展活动中,班上同学安排做成一个mm行nn列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了.幸运 ...

  9. ASP.NET通过EntityFramework CodeFirst创建数据库

    Number1 新建一个项目 给新项目添加一个实体数据模型 选择第三个 这里我创建两个有关系的类,也就是有外键关系的数据库表 using System; using System.Collection ...

  10. Java 第十一届 蓝桥杯 省模拟赛 递增序列

    问题描述 在数列 a[1], a[2], -, a[n] 中,如果 a[i] < a[i+1] < a[i+2] < - < a[j],则称 a[i] 至 a[j] 为一段递增 ...