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