TCP通讯是流式的,在发送一个大数据包时,可能会被拆分红多个数据包进行发送,同时,屡次发送数据包,也可能会被底层合并成一个数据包进行发送。git
所以,TCP通讯时须要设定通讯协议来正确处理收到的数据,如咱们常见的HTTP、FTP协议等。github
在该协议下,一个数据包老是有一个定长的包头加一个包体构成,其中包头中会有一个字段说明包体或者整个包的长度。服务器收到数据后就能够按序解析出包头 > 包体/包长度 > 包体。服务器
程序默认使用包头最后一个字节描述包体长度,下面来看看核心代码实现:app
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~tcp