Custom Protocol and Golang Implementation

Keywords: Go REST network socket

Write here is to make a summary for easy viewing when forgetting, and if there are mistakes in the article, please spray me haha, welcome to correct. It would be nice to help others.

Agreement

The so-called protocol is to specify a series of rules that enable the two or more parties who want to communicate to communicate normally. If we speak Chinese as agreement, if we do not speak in accordance with the rules of Chinese, the communicator will not understand what the other party says. Chinese and English are different agreements. If we use Chinese to communicate with people who can only speak English, others will not understand what you say. In computer, input and output parameters are protocols, and the simplest rest interface of server is protocols. The system implements a layer-by-layer network protocol to enable computers to communicate with each other.

Custom Protocol

Custom protocol is a protocol built on the existing protocol to meet the communication needs of our own programs when the existing protocol can not meet our needs. The current operating system will implement the underlying network protocol we need well, which is transparent to us. But when we write rest, do we need to define the interface? This interface is a kind of protocol we customize. By defining request parameters and response parameters, the client and the clothes we write well. The client program interacts, which is a custom protocol.

Define custom protocols

Custom protocol based on TCP mainly specifies a header for both sides of communication, and the data transmitted behind the header. Some parameters are specified in the header. For example, the first 10 bytes of the message are the header, and the first 2 bytes are fixed magic numbers. Let you know that I communicate according to this protocol. If I do not support this protocol, I can turn off the connection directly. Four bytes of magic number is the length of the data in the whole package. This length does not include the head. The reason for the length is that TCP protocol is oriented to the stream. It will throw the applications one by one to each other. If we don't use the length field, we will probably get the data of multiple packages. The last four bytes are a check code, checking whether the data of the received package is wrong, mainly to prevent tampering with the communication.
At this point, we have completed the definition of our custom protocol. The communication between the two sides can be completed by adding a header before the data is written to TCP at the sender and taking data according to the definition of the header when the receiver receives the data.

Definition Protocol in GO

A Conn structure is customized, which contains connection and metadata. Scan is used to get data from real socket, connect a new scan through socket, and register a function to split data through the split method of scan. This function is packaged in RegisterSplitFunc() function. Scan will pass through our registration letter. The data is divided by the method of segmentation in the number. Scan() is used to segment the data once, and scan.Bytes() is used to obtain the returned data.

type Conn struct {
    c    net.Conn
    meta metadata.Metadata
    scan *bufio.Scanner
}

func (c *Conn) Read() (b []byte, err error) {
    if c.c == nil {
        return nil, errors.New("conn is nil")
    }

    if c.scan == nil {
        //The wrapped function does a scan.Split(splitFunc) operation
        c.RegisterSplitFunc()
    }

    if c.scan.Scan() {
        b = c.scan.Bytes()
    } else {
        c.c.Close()
        err = errors.New("conn scan error")
    }
    return
}
//The head of write method consists of modulus 0x0102 and length len(data)+4.
//Follow the data behind your head
func (c *Conn) Write(data []byte) (err error) {
    if len(data) > math.MaxUint16 {
        return errors.New("data too big")
    }

    buf := bytes.Buffer{}
    binary.Write(&buf, binary.LittleEndian, []byte{0x01,0x02})
    binary.Write(&buf, binary.LittleEndian, uint16(len(data)+4))
    binary.Write(&buf, binary.BigEndian, data)
    _, err = c.c.Write(buf.Bytes())
    return
}

Partition function

func MySplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
    //Judge length, magic number
    if len(data) < 4 || data[0] != 0x88 || data[1] != 0x94 {
        err = errors.New("protocol error")
        return
    }

    var header uint32
    //Get the header
    err = binary.Read(bytes.NewReader(data[:4]), binary.BigEndian, &header)
    if err != nil {
        logs.Error("binary.Read error(%v)", err)
        return
    }

    l := uint16(header)
    //Making Size-to-Size Conversion
    l = (l >> 8) | (l << 8)
    //Read data by length. advance is read length, including header and data. Data is read data.
    if int(l) <= len(data) {
        advance, token, err = int(l), data[:int(l)], nil
    }
    if atEOF {
        err = errors.New("EOF")
    }

    return
}

summary

This is how to customize the simple communication protocol in golang.

Posted by devil_online on Tue, 06 Aug 2019 04:14:07 -0700