粘包產生的緣由有發送方和接收方兩方面:
服務端代碼以下:html
// socket_test/server/main.go func process(conn net.Conn) { defer conn.Close() // 使用bufio的讀緩衝區(防止系統緩衝區溢出) reader := bufio.NewReader(conn) var buf [1024]byte for { n, err := reader.Read(buf[:]) if err == io.EOF { break } if err != nil { fmt.Println("讀取客戶數據失敗,err:", err) break } recvData := string(buf[:n]) fmt.Println("收到client發來的數據:", recvData) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:12345") if err != nil { fmt.Println("監聽失敗, err:", err) return } defer listen.Close() for { conn, err := listen.Accept() if err != nil { fmt.Println("創建會話失敗, err:", err) continue } // 單獨建一個goroutine來維護客戶端的鏈接(不會阻塞主線程) go process(conn) } }
客戶端代碼以下:算法
// socket_test/client/main.go func main() { conn, err := net.Dial("tcp", "127.0.0.1:12345") if err != nil { fmt.Println("鏈接服務器失敗, err", err) return } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` conn.Write([]byte(msg)) } }
將以上服務器和客戶端的代碼分別編譯並運行,結果以下:後端
收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you? 收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? 收到client發來的數據: Hello, Hello. How are you?Hello, Hello. How are you?
咱們在客戶端發送了10次數據,但在服務器在輸出了5次,多條數據「粘」到了一塊兒。bash
基於以前的分析,咱們只要給每一個數據包封裝一個包頭,包頭裏只需包含數據包長度的信息,這樣接收方在收到數據時就能夠先讀取包頭的數據,經過包頭就能夠定位數據包的邊界,粘包問題也就迎刃而解。說白了,就是須要咱們定義一個應用之間通訊的協議,完成對每一個消息的編碼和解碼。代碼以下:服務器
// socket_test/proto/proto.go package proto import ( "bufio" "bytes" "encoding/binary" ) func Encode(msg string) ([]byte, error) { length := int32(len(msg)) // 建立一個數據包 pkg := new(bytes.Buffer) // 寫入數據包頭,表示消息體的長度 err := binary.Write(pkg, binary.LittleEndian, length) if err != nil { return nil, err } // 寫入消息體 err = binary.Write(pkg, binary.LittleEndian, []byte(msg)) if err != nil { return nil, err } return pkg.Bytes(), nil } func Decode(reader *bufio.Reader) (string, error) { // 讀取前4個字節的數據(表示數據包長度的信息) // peek操做只讀數據,但不會移動讀取位置!!! lengthByte, _ := reader.Peek(4) // 將前4個字節數據讀入字節緩衝區 lengthBuf := bytes.NewBuffer(lengthByte) var dataLen int32 // 讀取數據包長度 err := binary.Read(lengthBuf, binary.LittleEndian, &dataLen) if err != nil { return "", err } // 判斷數據包的總長度是否合法 if int32(reader.Buffered()) < dataLen + 4 { return "", err } pack := make([]byte, 4 + dataLen) // 讀取整個數據包 _, err = reader.Read(pack) if err != nil { return "", err } return string(pack[4:]), nil }
客戶端在發送消息時調用編碼函數對消息進行編碼,代碼以下:
// socket_test/client2/main.go網絡
package main import ( "fmt" "../proto" "net" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8888") if err != nil { fmt.Println("connect err=", err) return } fmt.Println("conn suc=", conn) for i := 0; i < 10; i++ { data, err := proto.Encode("hello, server!") if err != nil { fmt.Println("Encode failer, err = ", err) return } _, err = conn.Write(data) if err != nil { fmt.Println("send data failed, err= ", err) return } } }
服務器接收消息時調用解碼函數對消息進行解碼,代碼以下:socket
// socket_test/server2/main.go package main import ( "bufio" "fmt" "../proto" "io" "net" ) func process(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { msg, err := proto.Decode(reader) if err == io.EOF { return } if err != nil { fmt.Println("decode failed, err = ", err) return } fmt.Println("收到數據", msg) } } func main() { fmt.Println("服務器開始監聽......") listen, err := net.Listen("tcp", "127.0.0.1:8888") if err != nil { fmt.Println("listen err=", err) return } fmt.Printf("listen suc=%v\n", listen) // 延遲關閉 defer listen.Close() // 循環等待客戶端鏈接 for { fmt.Println("循環等待客戶端鏈接...") conn, err := listen.Accept() if err != nil { fmt.Println("Accept() err=", err) } else { fmt.Printf("Accept() suc=%v, 客戶端ip=%v\n", conn, conn.RemoteAddr().String()) } // 建立goroutine處理客戶端鏈接 go process(conn) } }
測試結果以下:tcp
收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server! 收到client發來的數據: hello, server!
能夠看到,此時服務器接收的數據已經沒有了粘包。函數
我是lioney,年輕的後端攻城獅一枚,愛鑽研,愛技術,愛分享。
我的筆記,整理不易,感謝閱讀、點贊和收藏。
文章有任何問題歡迎你們指出,也歡迎你們一塊兒交流後端各類問題!