Golang通过包长协议处理TCP粘包
tcp粘包产生的原因这里就不说了,因为大家能搜索TCP粘包的处理方法,想必大概对TCP粘包有了一定了解,所以我们直接从处理思路开始讲起
tcp粘包现象代码重现
首先,我们来重现一下TCP粘包,然后再此基础之上解决粘包的问题,这里给出了client和server的示例代码如下
/*文件名:client.goclient客户端的示例代码(未处理粘包问题)通过无限循环无时间间隔发送数据给server服务器server将会不间断的出现TCP粘包问题
*/
package main
import ("fmt""net"
)
func main() {conn, err := net.Dial("tcp", ":9000")if err != nil {return}defer conn.Close()for {s := "Hello, Server!"n, err := conn.Write([]byte(s))if err != nil {fmt.Println("Error:", err)fmt.Println("Error N:", n)return}// 这里通过限制发送频率和时间间隔来解决TCP粘包// 虽然能够实现,但是频率被限制,效率也会被限制// time.Sleep(time.Second * 1)}
}
/*文件名:server.goserver服务端的示例代码(未处理粘包问题)服务端接收到数据后立即打印此时将会不间断的出现TCP粘包问题
*/
package main
import ("fmt""net"
)
func main() {ln, err := net.Listen("tcp", ":9000")if err != nil {return}for {conn, err := ln.Accept()if err != nil {continue}go handleConnection(conn)}
}
func handleConnection(conn net.Conn) {defer conn.Close()tmp := []byte{}for {buf := make([]byte, 1024)n, err := conn.Read(buf)if err != nil {fmt.Println("Read Error:", err)fmt.Println("Read N:", n)return}fmt.Println(string(buf))}
}
按顺序启动server.go和client.go,正常情况下每行会输出Hello, World!
字样,出现TCP粘包后,将会出现类似Hello, World!Hello
之类的字样,后一个包粘到前一个包了
解决TCP粘包有很多种方法,归结起来就是通过自定义通讯协议来解决,例如分隔符协议、MQTT协议、包长协议等等,而我们这里介绍的就是通过包长协议来解决问题的,当然包长协议也有很多种自定义的方法
通过演示的结果,我们可以看出来,后一个包粘到了前一个包,而且后一个包不一定是一个完整的包,也很有可能第一次收到的数据包也不是完整的数据包
tcp粘包问题处理方法
这样我们就有必要校验每次收到的数据包是否是我们期望收到的,比较直观的,客户端和服务端双方协商某种协议,例如包长协议,在客户端发送数据时,先计算一下数据的长度(假设用2字节的uint16表示),然后将计算得到的长度和实际的数据组装成一个包,最后发送给服务端;而服务端接收到数据时,先读取2字节的数据长度信息(可能不足2字节,程序需要针对这种情况设计),然后根据数据长度来读取后边的数据(可能会存在数据过剩、数据刚好、数据不足等情况,程序需要针对这些情况设计)
有了思路之后,我们就需要对发送端和接收端的数据进行处理了,因为发送端较为简单,不需要考虑其他情况,只管封装数据包发送,所以这里我们先对发送端client进行处理
/*文件名:client.go使用包长协议,封装TCP包并循环发送给server服务端
*/
package main
import ("encoding/binary""fmt""net"
)
func main() {conn, err := net.Dial("tcp", ":9000")if err != nil {return}defer conn.Close()for {s := "Hello, Server!"sbytes := make([]byte, 2+len(s))binary.BigEndian.PutUint16(sbytes, uint16(len(s)))copy(sbytes[2:], []byte(s))n, err := conn.Write(sbytes)if err != nil {fmt.Println("Error:", err)fmt.Println("Error N:", n)return}// time.Sleep(time.Second * 1)}
}
按照我们的思路,首先使用len()
函数计算出待发送字符串的长度,然后使用make()
函数创建一个[]byte切片作为待组装发送的数据包缓存sbyte,长度就是2字节的包头+字符串的长度
,接着通过binary.BigEndian.PutUint16()
函数来对数据包缓存sbyte进行操作,将字符串的长度信息写入2字节的包头中,紧接着又通过copy()
完成封包组装,最后通过conn.Write()
将封包发送出去,这样子发送出去的数据大概长成下面的样子
[0][14][H][e][l][l][o][,][ ][S][e][r][v][e][r][!]
其中,封包整体长16bytes,Hello, Server!
则长14bytes
好了,至此数据将会循环不简短的发送给服务端,接下来我们就要对服务端server.go进行处理了,先上代码
/*文件名:server.go使用包长协议,处理接收到的封包数据收到的封包数据,可能存在几种情况:1、封包总长度不足2字节(这种情况不能完整获取包头),缓存起来与下次获取的数据拼接2、封包总长度刚好2字节,数据长度信息读出来是0,这种情况可以正常处理并清空缓存3、封包总长度大于2字节,数据长度信息大于封包数据实际长度,表示数据包不完整,需要等到下一次读取再拼接起来4、封包总长度大于2字节,数据长度信息等于封包数据实际长度,这种情况(理想情况)可以正常处理并清空缓存5、封包总长度大于2字节,数据长度信息小于封包实际长度,表示数据包发生TCP粘包了,读取实际数据后,将剩余部分缓存起来等待下次拼接PS:这里只总结出了这几种情况,其他未发现的情况还需另外处理
*/
package main
import ("encoding/binary""fmt""net"
)
func main() {ln, err := net.Listen("tcp", ":9000")if err != nil {return}for {conn, err := ln.Accept()if err != nil {continue}go handleConnection(conn)}
}
func handleConnection(conn net.Conn) {defer conn.Close()tmp := []byte{}for {buf := make([]byte, 1024)// fmt.Println("len:", len(buf), " cap:", cap(buf))n, err := conn.Read(buf)if err != nil {if e, ok := err.(*net.OpError); ok {fmt.Println(e.Source, e.Addr, e.Net, e.Op, e.Err)if e.Timeout() {fmt.Println("Timeout Error")}}fmt.Println("Read Error:", err)fmt.Println("Read N:", n)return}if n == 0 {fmt.Println("Read N:", n)return}tmp = append(tmp, buf[:n]...)length := len(tmp)if length < 2 {continue}if length >= 2 {head := make([]byte, 2)copy(head, tmp[:2])dataLength := binary.BigEndian.Uint16(head)data := make([]byte, dataLength)copy(data, tmp[2:dataLength+2])fmt.Println(string(data)) // 得到数据if uint16(length) == 2+dataLength {tmp = []byte{}} else if uint16(length) > 2+dataLength {tmp = tmp[dataLength+2:]}}// fmt.Println(string(buf))}
}
ps:这里的示例代码不能直接用于生产环境,只是提供tcp粘包处理的思路过程,代码还是存在一些问题的,例如server.go服务端还没有对第3种情况进行处理,封包总长度大于2字节,数据长度信息大于封包数据实际长度,表示数据包不完整,需要等到下一次读取再拼接起来
Golang通过包长协议处理TCP粘包相关推荐
- Netty(二)——TCP粘包/拆包
转载请注明出处:http://www.cnblogs.com/Joanna-Yan/p/7814644.html 前面讲到:Netty(一)--Netty入门程序 主要内容: TCP粘包/拆包的基础知 ...
- TCP粘包和拆包原因
最近研究Netty网络编程,以前项目中页遇到过数据接收过程中数据质量太差问题,很可能是TCP传输过程中问题,特此记录. 问题产生 一个完整的业务可能会被TCP拆分成多个包进行发送,也有可能把多个小的包 ...
- 【Netty入门】TCP 粘包/拆包问题产生原因
TCP粘包/分包问题的由来 因为TCP是以流的方式来处理数据,一个完整的包可能会被TCP拆分成多个包进行发送,也可能把小的封装成一个大的数据包发送. 这样说可能比较抽象,下面举例来说明TCP拆包/粘包 ...
- Netty学习总结(5)——Netty之TCP粘包/拆包问题的解决之道
无论是服务端还是客户端,读取或者发送消息的时候,都需要考虑TCP底层的粘包/拆包机制. TCP粘包/拆包 TCP是个"流"协议. 流:没有界限的一串数据.如同河里的流水,它们是连成 ...
- TCP——粘包/拆包
TCP粘包/拆包 TCP是个"流"协议,所谓流,就是没有界限的一串数据.大家可以想想河里的流水,它们是连成一片的,其间并没有分界线.TCP底层并不了解上层业务数据的具体含义,它会根 ...
- TCP粘包/拆包问题
目录 TCP粘包/拆包 TCP粘包/拆包问题说明 TCP粘包/拆包发生的原因 粘包问题的解决策略 未考虑TCP粘包导致功能异常案例 TimeServer的改造 TimeClient的改造 利用Lin ...
- TCP粘包、半包原理及解决方案
引言:TCP协议是网络通信协议中十分重要的协议,相比于UDP协议来说,它是一个可靠的传输协议,并且是一个面向数据流的协议:所谓面向数据流,其实是指数据传输是以流式的方式传输,这些传输的数据就像一条河里 ...
- Netty权威指南(四)TCP粘包/拆包问题
TCP粘包/拆包问题解决之道 上一章 一.介绍 1.1 TCP粘包/拆包问题说明 1.2 TCP粘包/拆包发生的原因 1.3 粘包问题的解决策略 二.未考虑TCP粘包导致的功能异常案例 2.1 Tim ...
- Netty详解(五):Netty TCP粘包 拆包
1. 概述 无论是服务端还是客户端,我们读取或者发送消息的时候,都需要考虑TCP底层的粘包和拆包机制.下面我们来通过Netty来详解TCP底层的粘包和拆包机制. 2. TCP底层的粘包和拆包机制 TC ...
- 关于TCP粘包的拙见
概述 本文主要概述TCP粘包的原因和如何解决TCP粘包的问题. TCP粘包原因 由于TCP是字节流传输协议,又没有保护边界,传输过程中为了提高传输效率,其采用了一种优化方式,将发送时间间隔小数据量小的 ...
最新文章
- java自动雨刷系统,安装雨量传感器实现自动大灯/自动雨刷(详细方法)多图!!
- spring storedProcedure 使用
- 《黑天鹅》读书笔记(part3)--那些声称注重过程而非结果的人并没有完全讲真话
- c# mysql ef框架_首页 C# EF6数据库第一-EF试图创建我的数据...
- 从结构到模块!华为提出最新两步搜索的目标检测SM-NAS
- javascript class constructor
- 「雅礼集训 2018 Day2」农民
- DHCP:(3)思科防火墙ASA上部署DHCP服务以及DHCP中继
- 手机功能测试主要测哪些方面?
- js去空格 回车 制表符 换页符
- 【转载】用Pwnage + Redsnow 制作完美越狱固件
- find函数的使用方法Matlab,matlab中find函数的使用说明——emily语法介绍
- 为什么可以做Shopyy独立站
- 【支付】银行卡支付的行为主体介绍
- HDU_1709 The Balence (生成函数)
- ios苹果机系统 的1px边框不显示
- 矢量数据shp七个文件介绍_读取矢量数据
- ipa文件包获取服务器地址,iOS获取App ipa包以及资源文件
- 浅谈STM32 USART串口中断配置函数USART_ITConfig()的编程思路
- 多个相机拍摄定位_多相机视觉系统的坐标系统标定与统一及其应用