Golang通过包长协议处理TCP粘包的问题解决
作者:地下十一楼的森琦 发布时间:2024-04-30 10:00:11
tcp粘包产生的原因这里就不说了,因为大家能搜索TCP粘包的处理方法,想必大概对TCP粘包有了一定了解,所以我们直接从处理思路开始讲起
tcp粘包现象代码重现
首先,我们来重现一下TCP粘包,然后再此基础之上解决粘包的问题,这里给出了client和server的示例代码如下
/*
文件名:client.go
client客户端的示例代码(未处理粘包问题)
通过无限循环无时间间隔发送数据给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.go
server服务端的示例代码(未处理粘包问题)
服务端接收到数据后立即打印
此时将会不间断的出现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
来源:https://blog.csdn.net/wudics/article/details/125324371


猜你喜欢
- Hello,各位读者朋友们好啊,我是小张~这不国庆嘛,就把最近很火的一个韩剧《鱿鱼游戏》刷了下,这部剧整体剧情来说还是非常不错的,很值得一看
- 本文主要介绍使用Python调用ADB命令实现实时监控logcat关键字的功能采用多进程,可同时监控多个设备,监控多个关键字。需要配置ADB
- 前言在Python中已经内置了一个smtp邮件发送模块,Django在此基础上进行了简单地封装,让我们在Django环境中可以更方便更灵活的
- 因此,在数据库的日常维护工作中,如果只是一次两次碰到ORA-01555错误,一般都先忽略,但是如果经常碰到该错误,则要进行一些调整以避免该错
- python读取和保存图片5种方法对比python中对象之间的赋值是按引用传递的,如果需要拷贝对象,需要用到标准库中的copy模块方法一:利
- 简介:numba是Anaconda公司开发的针对Python的开源JIT编译器,用于提供Python版CPU和GPU编程,速度比原生Pyth
- 一、状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类1.基本实现//下面以一个开灯程序演示状态模式//灯共用三
- 热的东西显然会变凉。房间会会人沮丧地变得凌乱。几乎同样,消息会失真。逆转这些情况的短期策略分别是重新加热、 做卫生和使用 Hop
- pycharm from lxml import etree标红##原因:没有lxml这个包###解决方法:需要安装xlml包####下载地
- 相信大家对python-docx这个常用的操作docx文档的库都不陌生,它支持以内联形状(Inline Shape)的形式插入图片,即图片和
- 一、分类问题损失函数——交叉熵(crossentropy)交叉熵刻画了两个概率分布之间的距离,是分类问题中使用广泛的损失函数。给定两个概率分
- 前言自 MySQL5.1.6起,增加了一个非常有特色的功能–事件调度器(Event Scheduler),可以用做定时执行某些特定任务(例如
- 本文实例讲述了MySQL从命令行导入SQL脚本时出现中文乱码的解决方法。分享给大家供大家参考,具体如下:在图形界面管理工具 MySql Qu
- 用mysqldump和source可以使用这种方式导出数据:mysqldump -urott -P5678 --default-charac
- 对数据库的操作无非就是增删改查,其中数查询操作最为复杂,所以将查询单独讲解,我这里用的Mysql数据库增删改查操作分页查询操作1.查询结果以
- 本文实例讲述了Python二叉树定义与遍历方法。分享给大家供大家参考,具体如下:二叉树基本概述:二叉树是有限个元素的几个,如果为空则为空二叉
- paramiko 执行服务器脚本并拿到实时结果import paramikocmd = '{0}/{1} linux 32'
- 目录赋值语句直接赋值:增量赋值: 链式赋值: 多重赋值:语法糖:基本输入:input()函数:eval()函数:&nbs
- 使用cpan安装Net::SSH::Perl:cpan>install Net::SSH::Perl期间遇到了一些问题,记录在此,以备
- 本文实例讲述了JS+HTML5实现上传图片预览效果。分享给大家供大家参考,具体如下:在项目中遇到用input标签file类型的文件上传,想实