GoMQTT服务器 tcp处理粘包问题
第一章 tcp处理粘包问题
写的不好,请指出,目前还在学习当中
文章目录
GoMQTT服务器 tcp处理粘包问题前言一、问题显示1.服务器代码2.客户端代码3.问题
二、分析1.为什么会出现粘包2.解决办法编解码修改服务端代码修改客户端代码服务端接收数据效果
前言
提示:这里可以添加本文要记录的大概内容: 例如:随着人工智能的不断发展,机器学习这门技术也越来越重要,很多人都开启了学习机器学习,本文就介绍了机器学习的基础内容。
提示:以下是本篇文章正文内容,下面案例可供参考
一、问题显示
1.服务器代码
func process(conn net
.Conn
) {
defer conn
.Close()
reader
:= bufio
.NewReader(conn
)
var buf
[1024]byte
for {
n
, err
:= reader
.Read(buf
[:])
if err
== io
.EOF
{
break
}
if err
!= nil {
fmt
.Println("read from client failed, err:", err
)
break
}
recvStr
:= string(buf
[:n
])
fmt
.Println("收到client发来的数据:", recvStr
)
}
}
func main() {
listen
, err
:= net
.Listen("tcp", "127.0.0.1:8088")
if err
!= nil {
fmt
.Println("listen failed, err:", err
)
return
}
defer listen
.Close()
for {
conn
, err
:= listen
.Accept()
if err
!= nil {
fmt
.Println("accept failed, err:", err
)
continue
}
go process(conn
)
}
}
2.客户端代码
func main() {
conn
, err
:= net
.Dial("tcp", "127.0.0.1:8088")
if err
!= nil {
fmt
.Println("dial failed, err", err
)
return
}
defer conn
.Close()
for i
:= 0; i
< 20; i
++ {
msg
:= `Hello, Hello. HelloWord?`
conn
.Write([]byte(msg
))
}
}
先启动服务端,再启动客户端
3.问题
收到client发来的数据: Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?
收到client发来的数据: Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?
收到client发来的数据: Hello, Hello. HelloWord?Hello, Hello. HelloWord?
收到client发来的数据: Hello, Hello. HelloWord?Hello, Hello. HelloWord?Hello, Hello. HelloWord?
收到client发来的数据: Hello, Hello. HelloWord?Hello, Hello. HelloWord?
客户端分20次发送的数据,在服务端并没有成功的输出20次,而是多条数据“粘”到了一起。
二、分析
1.为什么会出现粘包
主要原因就是tcp数据传输模式是流模式,在保持长连接的时候可以进行多次的收和发。 “粘包”可发生在发送端也可发生在接收端:主要有以下情况:
由Nagle算法造成的发送端的粘包
Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
接收端接收不及时造成的接收端粘包
TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层不能及时的把TCP的数据取出,就会造成TCP缓冲区中存放了几段数据。
2.解决办法
出现”粘包”的关键在于接收方不确定将要传输来的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。
封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。
我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度,后面数据域长度的内容为实际传输的消息。
| 包头| 包体|
编解码
因此,可以写一个编解码器,参考了Java netty
package proto
import (
"bufio"
"bytes"
"encoding/binary"
)
func Encode(message
*string)([]byte,error ) {
var lengrh
= int32(len(*message
))
var pkg
= new(bytes
.Buffer
)
err
:= binary
.Write(pkg
,binary
.LittleEndian
,lengrh
)
if err
!= nil{
return nil,err
}
err
= binary
.Write(pkg
,binary
.LittleEndian
,[]byte(*message
))
if err
!= nil{
return nil,err
}
return pkg
.Bytes(),nil
}
func Decode(reader
*bufio
.Reader
) (string,error) {
lengthByte
, _ := reader
.Peek(4)
lengthBuffer
:= bytes
.NewBuffer(lengthByte
)
var length
int32
err
:= binary
.Read(lengthBuffer
,binary
.LittleEndian
,&length
)
if err
!= nil{
return "", err
}
if int32(reader
.Buffered()) < length
+ 4{
return "",err
}
pack
:= make([]byte, int(4+length
))
_, err
= reader
.Read(pack
)
if err
!= nil{
return "", err
}
return string(pack
[4:]),nil
}
修改服务端代码
package main
import (
"bufio"
"fmt"
"io"
"net"
"study_one_day/gotcp/proto"
)
func main() {
listen
, err
:= net
.Listen("tcp","127.0.0.1:8088")
if err
!= nil{
fmt
.Println("listen failed, err:",err
)
}
fmt
.Println("begin listen...")
for{
conn
, err
:= listen
.Accept()
if err
!= nil{
fmt
.Println("accept failed, err:",err
)
}
go processPlus(conn
)
}
}
func processPlus(conn net
.Conn
) {
defer conn
.Close()
reader
:= bufio
.NewReader(conn
)
for{
msg
, err
:= proto
.Decode(reader
)
if err
== io
.EOF
{
fmt
.Println("client close connect")
return
}
if err
!= nil{
fmt
.Println("decode msg failed, err : ",err
)
return
}
fmt
.Println("收到client发来的数据: ",msg
)
}
}
修改客户端代码
package main
import (
"bufio"
"fmt"
"net"
"strings"
"study_one_day/gotcp/proto"
)
func main() {
conn
, err
:= net
.Dial("tcp","127.0.0.1:8088")
if err
!= nil {
fmt
.Println("dial failed, err", err
)
return
}
defer conn
.Close()
for i
:= 0; i
< 20; i
++ {
msg
:= "Hello Hello HelloWorld ?"
data
, err
:= proto
.Encode(&msg
)
if err
!= nil{
fmt
.Println("encode msg failed, err : ",err
)
return
}
conn
.Write(data
)
}
}
服务端接收数据效果
begin listen
...
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
收到client发来的数据: Hello Hello HelloWorld ?
client close connect
可见,效果不错