Achieve Redis protocol parser

This article is "implemented Golang a Redis" The second series of articles, this article will introduce Redis communication protocol and protocol parser implementation. If you understand the protocol parser can read part of the direct agreement.

Redis protocol

Redis since version 2.0 uses a unified protocol RESP (REdis Serialization Protocol), the protocol is easy to implement, the computer can be resolved efficiently and easily understand humans.

RESP is a binary protocol secure text, work on the TCP protocol. Command or data sent by the server and the client shall be to \r\n(CRLF) at the end.

RESP defines five formats:

  • Simple string (Simple String): the server to return the results of the simple, such as "OK". Non-binary safe and does not allow line breaks.
  • Error Message (Error): a simple server to return results, such as "ERR Invalid Synatx". Non-binary safe and does not allow line breaks.
  • Integer (Integer): llen, scardand the like has a return value, 64-bit signed integer
  • String (Bulk String): binary safe string, getsuch as the return value of the command
  • An array (Array, legacy document called Multi Bulk Strings): Bulk String array, the client sends instructions and lrangecommands and response formats

RESP to represent the format by the first letter:

  • Simple string: the "+" start, such as: "+ OK \ r \ n"
  • Error: "-" for the start, such as: "- ERR Invalid Synatx \ r \ n"
  • Integer: A ":" Start, such as: ": 1 \ r \ n"
  • String: to $begin
  • Array: to *begin

Bulk String two rows, the first row $+ body length, the actual behavior of the second content. Such as:

$3\r\nSET\r\n

Bulk String binary byte may contain any security, that may be included within the Bulk String "\ r \ n" characters (CRLF end of the line is hidden):

$4
a\r\nb

$-1Represents nil, such as when using the get command to query the key does not exist, it is the response $-1.

Array Format The first line "*" + length of the array, followed by a corresponding number of Bulk String. For example, ["foo", "bar"]the message:

*2
$3
foo
$3
bar

The client can also send commands to the server using the Array format. The command itself as the first parameter, such as the SET key valueRESP message instruction:

*3
$3
SET
$3
key
$5
value

The print out newline:

*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

Protocol parser

We realize TCP server in an article has been introduced to achieve TCP server protocol parser will achieve its Handler interface acts as a server application layer.

Socket protocol parser will receive the transmitted data, and restore it to the data [][]byteformat, as "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\value\r\n"will be reduced to ['SET', 'key', 'value'].

The complete code for this article: Github: HDT3213 / Godis

Request from the client are array format, in which a first number of rows in the row labeled packets and use CRLFas line breaks.

bufioThe standard library reader can read data from the cache to the buffer, the separator or read the complete return until after the encounter, so we use reader.ReadBytes('\n')to ensure that each read to complete the line.

Note that the RESP is 二进制安全protocol that allows the use in the text CRLFcharacters. For example Redis may receive and execute the proper SET "a\r\nb" 1instruction that the correct message is this:

*3  
$3
SET
$4
a\r\nb 
$7
myvalue

When ReadBytesreading the fifth line "a \ r \ nb \ r \ n" will mistake it two lines:

*3  
$3
SET
$4
a  // 错误的分行
b // 错误的分行
$7
myvalue

Therefore, when the fourth row is read to the $4rear, you should not continue ReadBytes('\n')to read the next line should be used io.ReadFull(reader, msg)to read the contents of the specified length method.

msg = make([]byte, 4 + 2) // 正文长度4 + 换行符长度2
_, err = io.ReadFull(reader, msg)

It defines Clientthe structure as a client abstraction:

type Client struct {
    /* 与客户端的 Tcp 连接 */
    conn   net.Conn

    /* 
     * 带有 timeout 功能的 WaitGroup, 用于优雅关闭
     * 当响应被完整发送前保持 waiting 状态, 阻止链接被关闭
     */
    waitingReply wait.Wait

    /* 标记客户端是否正在发送指令 */ 
    sending atomic.AtomicBool
    
    /* 客户端正在发送的参数数量, 即 Array 第一行指定的数组长度 */
    expectedArgsCount uint32
    
    /* 已经接收的参数数量, 即 len(args)*/ 
    receivedCount uint32
    
    /*
     * 已经接收到的命令参数,每个参数由一个 []byte 表示
     */
    args [][]byte
}

Parser definitions:

type Handler struct {

    /* 
     * 记录活跃的客户端链接 
     * 类型为 *Client -> placeholder 
     */
    activeConn sync.Map 

    /* 数据库引擎,执行指令并返回结果 */
    db db.DB

    /* 关闭状态标志位,关闭过程中时拒绝新建连接和新请求 */
    closing atomic.AtomicBool 
}

Then you can write the main parts:

func (h *Handler)Handle(ctx context.Context, conn net.Conn) {
    if h.closing.Get() {
        // 关闭过程中不接受新连接
        _ = conn.Close()
    }

    /* 初始化客户端状态 */
    client := &Client {
        conn:   conn,
    }
    h.activeConn.Store(client, 1)

    reader := bufio.NewReader(conn)
    var fixedLen int64 = 0 // 将要读取的 BulkString 的正文长度
    var err error
    var msg []byte
    for {
        /* 读取下一行数据 */ 
        if fixedLen == 0 { // 正常模式下使用 CRLF 区分数据行
            msg, err = reader.ReadBytes('\n')
            // 判断是否以 \r\n 结尾
            if len(msg) == 0 || msg[len(msg) - 2] != '\r' {
                errReply := &reply.ProtocolErrReply{Msg:"invalid multibulk length"}
                _, _ =  client.conn.Write(errReply.ToBytes())
            }
        } else { // 当读取到 BulkString 第二行时,根据给出的长度进行读取
            msg = make([]byte, fixedLen + 2)
            _, err = io.ReadFull(reader, msg)
            // 判断是否以 \r\n 结尾
            if len(msg) == 0 || 
              msg[len(msg) - 2] != '\r' ||  
              msg[len(msg) - 1] != '\n'{
                errReply := &reply.ProtocolErrReply{Msg:"invalid multibulk length"}
                _, _ =  client.conn.Write(errReply.ToBytes())
            }
            // Bulk String 读取完毕,重新使用正常模式
            fixedLen = 0 
        }
        // 处理 IO 异常
        if err != nil {
            if err == io.EOF || err == io.ErrUnexpectedEOF {
                logger.Info("connection close")
            } else {
                logger.Warn(err)
            }
            _ = client.Close()
            h.activeConn.Delete(client)
            return // io error, disconnect with client
        }

        /* 解析收到的数据 */
        if !client.sending.Get() { 
            // sending == false 表明收到了一条新指令
            if msg[0] == '*' {
                // 读取第一行获取参数个数
                expectedLine, err := strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)
                if err != nil {
                    _, _ = client.conn.Write(UnknownErrReplyBytes)
                    continue
                }
                // 初始化客户端状态
                client.waitingReply.Add(1) // 有指令未处理完成,阻止服务器关闭
                client.sending.Set(true) // 正在接收指令中
                // 初始化计数器和缓冲区 
                client.expectedArgsCount = uint32(expectedLine) 
                client.receivedCount = 0
                client.args = make([][]byte, expectedLine)
            } else {
                // TODO: text protocol
            }
        } else {
            // 收到了指令的剩余部分(非首行)
            line := msg[0:len(msg)-2] // 移除换行符
            if line[0] == '$' { 
                // BulkString 的首行,读取String长度
                fixedLen, err = strconv.ParseInt(string(line[1:]), 10, 64)
                if err != nil {
                    errReply := &reply.ProtocolErrReply{Msg:err.Error()}
                    _, _ = client.conn.Write(errReply.ToBytes())
                }
                if fixedLen <= 0 {
                    errReply := &reply.ProtocolErrReply{Msg:"invalid multibulk length"}
                    _, _ = client.conn.Write(errReply.ToBytes())
                }
            } else { 
                // 收到参数
                client.args[client.receivedCount] = line
                client.receivedCount++
            }


            // 一条命令发送完毕
            if client.receivedCount == client.expectedArgsCount {
                client.sending.Set(false)

                // 执行命令并响应
                result := h.db.Exec(client.args)
                if result != nil {
                    _, _ = conn.Write(result.ToBytes())
                } else {
                    _, _ = conn.Write(UnknownErrReplyBytes)
                }

                // 重置客户端状态,等待下一条指令
                client.expectedArgsCount = 0
                client.receivedCount = 0
                client.args = nil
                client.waitingReply.Done()
            }
        }
    }
}

Guess you like

Origin www.cnblogs.com/Finley/p/11923168.html