TCP分包、合包的场景
TCP通信是流式的,在发送一个大数据包时,可能会被拆分成多个数据包进行发送,同时,多次发送数据包,也可能会被底层合并成一个数据包进行发送。
- 分包:接收一个数据包,需要对数据包进行拆分;
- 合包:接收多个数据包,需要对数据包进行合并;
因此,TCP通信时需要设定通信协议来正确处理收到的数据,如我们常见的HTTP、FTP协议等。
固定包头+包体方式
在该协议下,一个数据包总是有一个定长的包头加一个包体构成,其中包头中会有一个字段说明包体或者整个包的长度。服务器收到数据后就可以按序解析出包头 > 包体/包长度 > 包体。
程序默认使用包头最后一个字节描述包体长度,下面来看看核心代码实现:
package tcp_package
import (
"encoding/binary"
"fmt"
"net"
)
type Reader struct {
Conn net.Conn
Buff []byte //数据接收缓冲区
Start int //数据读取开始位置
End int //数据读取结束位置
BuffLen int //数据接收缓冲区大小
HeaderLen int //包头长度
LengthOffset int //指示包体长度的字段在包头中的位置(2字节)
Message chan string //单次接收的完整数据
}
func NewReader(conn net.Conn, maxBufferSize, headerLen, lengthOffset int) (*Reader, error) {
if lengthOffset+2 > headerLen {
return nil, fmt.Errorf("incorrect 'headerLen' or 'lengthOffset'")
}
return &Reader{
Conn: conn,
Buff: make([]byte, maxBufferSize),
Start: 0,
End: 0,
BuffLen: maxBufferSize,
HeaderLen: headerLen,
LengthOffset: lengthOffset,
Message: make(chan string, 10),
}, nil
}
func (r *Reader) Do() (err error) {
defer close(r.Message)
err = r.read()
if err != nil {
return fmt.Errorf("read data error:%v", err)
}
return
}
//读取tcp数据流
func (r *Reader) read() error {
for {
r.move()
if r.End == r.BuffLen {
//缓冲区的宽度容纳不了一条消息的长度
return fmt.Errorf("message is too large:%v", r)
}
length, err := r.Conn.Read(r.Buff[r.End:])
if err != nil {
return err
}
r.End += length
r.readFromBuff()
}
}
//前移上一次未处理完的数据
func (r *Reader) move() {
if r.Start == 0 {
return
}
copy(r.Buff, r.Buff[r.Start:r.End])
r.End -= r.Start
r.Start = 0
}
//读取buff中的单条数据
func (r *Reader) readFromBuff() {
if r.End-r.Start < r.HeaderLen {
//包头的长度不够,继续接收
return
}
//读取包头数据
headerData := r.Buff[r.Start:(r.Start + r.HeaderLen)]
//读取包体的长度(2字节)
bodyLen := binary.BigEndian.Uint16(headerData[r.LengthOffset : r.LengthOffset+2])
if r.End-r.Start-r.HeaderLen < int(bodyLen) {
//包体的长度不够,继续接收
return
}
//读取包体数据
bodyData := r.Buff[(r.Start + r.HeaderLen) : r.Start+r.HeaderLen+int(bodyLen)]
//把完整的数据包用通道传递出去
r.Message <- string(append(headerData, bodyData...))
//每读完一次数据 start 后移
r.Start += r.HeaderLen + int(headerData[r.HeaderLen-1])
r.readFromBuff()
}
完整项目放在Github上,欢迎给Star~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。