TCP sticky packet based on Go language
Sticky bag example
Server:
package main import ( "bufio" "fmt" "io" "net" ) func Process2(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("received client Data sent:", recvStr) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:9999") fmt.Println("Listening port") if err != nil { fmt.Println("listen failed err: ", err) } for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed err: ", err) continue } go Process2(conn) } }
client:
package main import ( "fmt" "net" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:9999") if err != nil{ fmt.Println("err: ", err) } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` conn.Write([]byte(msg)) } }
Server result:
The data sent by the client in 10 times is not successfully output 10 times at the server, but multiple pieces of data are "stuck" together.
Listening port received client Data sent: 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?Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you?Hello, Hello. How are you?Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you? received client Data sent: Hello, Hello. How are you?
Why are there sticky bags
The main reason is that tcp data transfer mode is stream mode, which can receive and send multiple times when maintaining a long connection.
"Sticky packet" can occur at the sending end or at the receiving end:
- Sticky packets at the sender caused by Nagle algorithm: Nagle algorithm is an algorithm to improve network transmission efficiency. Simply put, when we submit a piece of data to TCP for transmission, TCP does not immediately send this piece of data, but waits for a short period of time to see if there is still data to be sent during the waiting period. If so, it will send these two pieces of data at one time.
- Packet sticking at the receiving end caused by untimely reception at the receiving end: TCP will store the received data in its own buffer, and then notify the application layer to fetch the data. When the application layer can't take out the TCP data in time for some reasons, several segments of data will be stored in the TCP buffer.
terms of settlement
The key to "sticking packets" is that the receiver is uncertain about the size of the packets to be transmitted, so we can packet and unpack the packets.
Packet: packet is to add a packet header to a piece of data, so that the packet can be divided into two parts: packet header and packet body (the "packet tail" content will be added to the packet when filtering illegal packets). The length of the packet header is fixed, and it stores the length of the packet body. According to the fixed length of the packet header and the variable containing the length of the packet body in the packet header, a complete packet can be correctly split.
We can define a protocol ourselves. For example, the first four bytes of a packet are the packet header, which stores the length of the transmitted data.
//proto.go // Encode encodes the message func Encode(message string)([]byte, error){ // The length of the read message is converted to int32 type (4 bytes) var length = int32(len(message)) var pkg = new(bytes.Buffer) // Write header err := binary.Write(pkg, binary.LittleEndian, length) fmt.Println("Write header", err, message ) if err != nil{ return nil, err } // Write message body err = binary.Write(pkg, binary.LittleEndian, []byte(message)) fmt.Printf("Write message body T%", err ) if err != nil{ return nil, err } return pkg.Bytes(), nil } // Decode decode message func Decode(reader *bufio.Reader)(string, error) { // Read message length lengthByte, _ := reader.Peek(4) // Read the first 4 bytes of data lengthBuff := bytes.NewBuffer(lengthByte) var length int32 err := binary.Read(lengthBuff, binary.LittleEndian, &length) if err !=nil{ return "", err } // Buffered returns the number of bytes that are currently readable in the buffer. if int32(reader.Buffered()) < length+4{ return "", err } // Read real messages pack := make([]byte, int(4+length)) _, err = reader.Read(pack) if err != nil{ return "", err } return string(pack[4:]), nil }
Next, the server and client use the Decode and Encode functions of the proto package defined above to process data respectively.
The server code is as follows:
package main import ( "bufio" "bytes" "encoding/binary" "fmt" "io" "net" ) func Process3(conn net.Conn) { defer conn.Close() reader := bufio.NewReader(conn) for { msg, err := Decode(reader) // decode if err == io.EOF { break } if err != nil { fmt.Println("read from client failed, err:", err) break } fmt.Println("received client Data sent:", msg) } } func main() { listen, err := net.Listen("tcp", "127.0.0.1:9999") fmt.Println("Listening port") if err != nil { fmt.Println("listen failed err: ", err) } for { conn, err := listen.Accept() if err != nil { fmt.Println("accept failed err: ", err) continue } go Process3(conn) } }
The client code is as follows:
func main() { conn, err := net.Dial("tcp", "127.0.0.1:9999") if err != nil{ fmt.Println("err: ", err) } defer conn.Close() for i := 0; i < 20; i++ { msg := `Hello, Hello. How are you?` data, err := Encode(msg) if err != nil { fmt.Println("encode msg failed, err:", err) return } conn.Write(data) } }