part 1

Recently, I was studying the RPC of GO. After reading it, I wanted to implement a CODEC, which is the serialization and deserialization of custom messages. Serialization and deserialization of messages involve two steps: 1. Read data from the network and write data to the network; 2. 2. Deserialize from the received binary data and serialize the existing objects to binary data. And this process needs to deal with TCP unpacking sticky packet.

TCP unpacking/sticky packet is also a relatively basic problem in network programming, the specific problem meaning and solution is no longer described in detail. Although programmers implementing application-layer logic probably don’t need to care about this at all, for middleware development purposes, as well as for the purpose of learning the GO language, I’ll do a little practice.

part 2

TCP unpacking sticky packet solution: when reading data, the binary data read can be segmented in the correct position. The head+body method is used directly, where the size of the entire data is appended to the data before it is sent, like this:

+++++++++++++++++++++++++++++++++++++
size (2 bytes)  | body (size bytes)
+++++++++++++++++++++++++++++++++++++
Copy the code

Note: in this example, size is 2 bytes; Size Specifies the number of bytes used based on the actual situation

part 3

Server implementation:

func doConn(conn net.Conn) {
    var (
        buffer = bytes.NewBuffer(make([]byte.0, BUF_SIZE)) // Buffer is used to cache the read data
        readBytes = make([]byte, BUF_SIZE) //readBytes is used to receive data from each read. ReadBytes are added to buffer after each read
        isHead = true // Indicates whether the size or body part is being processed
        bodyLen = 0 // Represents the length of the body
    )

    for {
        // Read the data first
        readByteNum, err := conn.Read(readBytes)
        iferr ! =nil {
            log.Fatal(err)
            return
        }
        buffer.Write(readBytes[0:readByteNum])// Put the read data into buffer
        
        // Then process the data
        for {
            if isHead {
                if buffer.Len() >= HEAD_SIZE {
                    isHead = false
                    head := make([]byte, HEAD_SIZE)
                    _, err = buffer.Read(head)
                    iferr ! =nil {
                        log.Fatal(err)
                        return
                    }
                    bodyLen = int(binary.BigEndian.Uint16(head))
                } else {
                    break; }}if! isHead {if buffer.Len() >= bodyLen {
                    body := make([]byte, bodyLen)
                    _, err = buffer.Read(body[:bodyLen])
                    iferr ! =nil {
                        log.Fatal(err)
                        return
                    }
                    fmt.Println("received body: " + string(body[:bodyLen]))
                    isHead = true
                } else {
                    break; }}}}func HandleTcp(a) {
	listener, err := net.Listen("tcp".": 1234")
	iferr ! =nil {
		log.Fatal(err)
		return
	}
	log.Println("start listening on 1234")
	for {
		conn, err := listener.Accept()
		iferr ! =nil {
			log.Fatal(err)
			return
		}
		go doConn(conn)
	}
}
Copy the code

Client concrete implementation:

func SendStringwithTcp(arg string) error {
	conn, err := net.Dial("tcp".": 1234")
	iferr ! =nil {
		log.Fatal(err)
		return err
	}

	head := make([]byte, server.HEAD_SIZE)
	content := []byte(arg)
	headSize := len(content)
	binary.BigEndian.PutUint16(head, uint16(headSize))

    // Write the head section first, then the body section
	_, err = conn.Write(head)
	iferr ! =nil {
		log.Fatal(err)
		return err
	}
	_, err = conn.Write(content)
	iferr ! =nil {
		log.Fatal(err)
		return err
	}
	return nil
}
Copy the code