go手写Redis(6)之协议解析

RESP协议解析

具体的协议说明可以参考第一篇文章协议说明, 下面我们直接开始协议的解析

Reply

interface/resp/reply.go 中定义一个顶级的回复实体接口,后续所有的协议类都将实现这个接口

// Reply 用于回复数据
type Reply interface {

	//ToBytes 将数据转换为Byte数组
	ToBytes() []byte
}

1. 通用响应

redis 中有许多的通用处理协议,例如:错误、正常响应、整数、数组等常用的实体,我们可以直接提前定义好,后续在解析协议的时候出现错误就可以直接使用

reply.go

resp/reply/reply.go 创建一个回复的文件,当前go文件中我们定义协议相关的实体结构体,例如:IntReply、StatusReply 等结构体,都实现了接口 ReplyToBytes() 方法

var (
	nullBulkReplyBytes = []byte("$-1")
	CRLF               = "\r\n" //结尾符号
)

// BulkReply 单个字符串的回复体
type BulkReply struct {
	//响应数据,如果要回复一个 "hello world" , $11\r\nhello world\r\n
	Arg []byte
}

// ToBytes 自定义实体
func (b *BulkReply) ToBytes() []byte {
	if len(b.Arg) == 0 {
		return nullBulkReplyBytes
	}
	return []byte(fmt.Sprintf("$%d%s%s%s", len(b.Arg), CRLF, string(b.Arg), CRLF))
}

// MakeBulkReply 创建一个实体
func MakeBulkReply(arg []byte) *BulkReply {
	return &BulkReply{
		Arg: arg,
	}
}

// MultiBulkReply 多个字符串的回复
type MultiBulkReply struct {
	Args [][]byte
}

func (m *MultiBulkReply) ToBytes() []byte {
	argLen := len(m.Args)
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("*%d%s", argLen, CRLF))
	for _, arg := range m.Args {
		if arg == nil {
			buf.WriteString(string(nullBulkReplyBytes) + CRLF)
		}
		buf.WriteString(fmt.Sprintf("$%d%s%s%s", len(arg), CRLF, string(arg), CRLF))
	}
	return buf.Bytes()
}

// MakeMultiBulkReply 创建一个实体
func MakeMultiBulkReply(args [][]byte) *MultiBulkReply {
	return &MultiBulkReply{
		Args: args,
	}
}

// StatusReply 状态回复
type StatusReply struct {
	Status string
}

func (s *StatusReply) ToBytes() []byte {
	return []byte("+" + s.Status + CRLF)
}

func MakeStatusReply(status string) *StatusReply {
	return &StatusReply{
		Status: status,
	}
}

// IntReply 数字类型回复
type IntReply struct {
	Code int64
}

func (i *IntReply) ToBytes() []byte {
	return []byte(":" + strconv.FormatInt(i.Code, 10) + CRLF)
}

func MakeIntReply(code int64) *IntReply {
	return &IntReply{Code: code}
}

// StandardErrReply 标准错误回复
type StandardErrReply struct {
	Status string
}

func (s *StandardErrReply) ToBytes() []byte {
	return []byte("-" + s.Status + CRLF)
}

func MakeStandardErrReply(status string) *StandardErrReply {
	return &StandardErrReply{Status: status}
}

// IsErrReply 判断回复的数据是否是异常的信息
func IsErrReply(reply resp.Reply) bool {
	return reply.ToBytes()[0] == '-'
}

// ErrorReply 错误回复,实现了两个接口
type ErrorReply interface {
	//Error 错误信息
	Error() string
	//ToBytes 转换字节数组
	ToBytes() []byte
}

error.go

在同目录下面我们创建一个错误回复的接口体定义文件,用于定义通用的错误接口体

/**
	通用异常信息
1. UnknownErrReply:未知错误
2. ArgNumErrReply:参数个数错误
3. SyntaxErrReply:语法错误
4. WrongTypeErrReply:错误类型
5. ProtocolErrReply:协议错误,不符合resp协议
*/

// UnknownErrReply 未知错误
type UnknownErrReply struct{}

var unKnownErrReply = []byte("-Err unknown\r\n")

func (u *UnknownErrReply) Error() string {
	return "Err unknown"
}

func (u *UnknownErrReply) ToBytes() []byte {
	return unKnownErrReply
}

func MakeUnknownErrReply() *UnknownErrReply {
	return &UnknownErrReply{}
}

// ArgNumErrReply 参数个数错误
type ArgNumErrReply struct {
	//Cmd 指令本身
	Cmd string
}

func (a *ArgNumErrReply) Error() string {
	return fmt.Sprintf("Err wrong number of arguments for : %s, command", a.Cmd)
}

func (a *ArgNumErrReply) ToBytes() []byte {
	return []byte(fmt.Sprintf("-Err wrong number of arguments for : %s, command\r\n", a.Cmd))
}

func MakeArgNumErrReply(cmd string) *ArgNumErrReply {
	return &ArgNumErrReply{Cmd: cmd}
}

// SyntaxErrReply 语法错误
type SyntaxErrReply struct{}

var syntaxErrBytes = []byte("-Err syntax error\r\n")
var theSyntaxErrReply = new(SyntaxErrReply)

func (s *SyntaxErrReply) Error() string {
	return "Err syntax error"
}

func (s *SyntaxErrReply) ToBytes() []byte {
	return syntaxErrBytes
}

func MakeSyntaxErrReply() *SyntaxErrReply {
	return &SyntaxErrReply{}
}

// WrongTypeErrReply 语法错误
type WrongTypeErrReply struct{}

var wrongTypeErrBytes = []byte("-wrong type operation against a key holding the wrong kind of value\r\n")
var theWrongTypeErrReply = new(WrongTypeErrReply)

func (w *WrongTypeErrReply) Error() string {
	return "-Err wrong type"
}

func (w *WrongTypeErrReply) ToBytes() []byte {
	return wrongTypeErrBytes
}

func MakeWrongTypeErrReply() *WrongTypeErrReply {
	return &WrongTypeErrReply{}
}

// ProtocolErrReply 协议错误
type ProtocolErrReply struct {
	Msg string
}

func (p *ProtocolErrReply) Error() string {
	return fmt.Sprintf("Error protocol error: %s\r\n", p.Msg)
}

func (p *ProtocolErrReply) ToBytes() []byte {
	return []byte(fmt.Sprintf("-Error protocol error: %s\r\n", p.Msg))
}

consts.go

这个文件中主要定义了一些固定的回复格式

/**
	用于保存一些固定的回复信息格式
1. PongReply:心跳回复
2. OkReply:ok回复
3. NullBulkReply:空字符串回复
4. EmptyMultiBulkReply:空数组回复
5. NoReply:无数据
*/

// PongReply 心跳回复
type PongReply struct{}

// pongBytes 创建常量字节数组
var pongBytes = []byte("+PONG\r\n")

// thePongReply 直接创建一个常量,不用每次都创建新的对象
var thePongReply = new(PongReply)

type OkReply struct{}

var okBytes = []byte("+OK\r\n")
var theOkReply = new(OkReply)

// NullBulkReply 空字符串的响应 -1代表空
type NullBulkReply struct{}

var nullBulkBytes = []byte("$-1\r\n")
var theNullBulkReply = new(NullBulkReply)

// EmptyMultiBulkReply 空数组回复
type EmptyMultiBulkReply struct{}

var emptyMultiBulkBytes = []byte("*0\r\n")
var theEmptyMultiBulkReply = new(EmptyMultiBulkReply)

// NoReply 空回复
type NoReply struct{}

var noReplyBytes = []byte("")
var theNoReply = new(NoReply)

// ToBytes 直接回复固定的结构数据
func (p *PongReply) ToBytes() []byte {
	return pongBytes
}

// MakePongReply 创建reply,一般暴露一个make方法出去
func MakePongReply() *PongReply {
	return thePongReply
}

func (o *OkReply) ToBytes() []byte {
	return okBytes
}

func MakeOkReply() *OkReply {
	return theOkReply
}

func (n *NullBulkReply) ToBytes() []byte {
	return nullBulkBytes
}

func MakeNullBulkReply() *NullBulkReply {
	return theNullBulkReply
}

func (e *EmptyMultiBulkReply) ToBytes() []byte {
	return emptyMultiBulkBytes
}

func MakeEmptyMultiBulkReply() *EmptyMultiBulkReply {
	return theEmptyMultiBulkReply
}

func (n *NoReply) ToBytes() []byte {
	return noReplyBytes
}

func MakeNoReply() *NoReply {
	return theNoReply
}

2. 解析器

2.1 结构体

Payload

Payload 结构体主要保存我们将协议数据解析完成之后的存储,里面包含了 resp.Replyerror 错误信息,Data 为响应实体就是上面定义的所有的结构体对象

readState

解析器的状态里面包含了当前解析协议参数描述

字段说明
  • readingMultiline:当前解析的数据是多行数据还是单行数据
  • expectedArgsCount:记录解析命令的参数个数,如果是数组那么解析的就是多个,如果是单行解析的就是1
  • msgType:数据包的类型。例如:*、+、$ 等数据包的类型,参考resp协议说明
  • args:传递的参数本身,二维数据进行数据的存储,例如:set k v,就有三个组
  • bulkLen:解析后续需要解析数据的字节长度
方法说明
  • finished:当前解析器是否已经解析完成,主要是判断 args的长度和expectedArgsCount是否相同
  • result:根据指令的类型保存结果返回出去,响应的是 resp.Reply 。注意我们对协议的解析都是操作的 resp.Reply 类型
  • ParseStream:这是一个公共方法,供外部调用里面主要创建一个 管道(chan) 返回给外部,解析完成之后就会往管道里面传递
  • parser0:解析方法,接受参数 io.Reader*Payload 两个类型,io.Reader 在前面tcp服务器实现的时候说过,客户的连接也会实现这个接口
  • readLine:每次读取一行,以 \n 结尾
  • adjustType:根据协议的类型会返回对应的处理函数
  • readBody:用于读取 $ 后续的真正的数据

以上就是大体的说明,实际解析都是按照 RESP 协议进行正常的解析,可以自行解析,下面是代码

/**
协议解析器
*/

// Payload 数据实体
type Payload struct {
	//客户端回复给服务端的数据,为什么都用Reply格式,因为对服务端来说,也是客户端回复给服务端的数据
	Data resp.Reply
	//是否有错误
	Err error
}

// readState 解析器的状态
type readState struct {
	//解析的是单行还是多行数据
	readingMultiline bool
	//记录解析命令的参数个数,如果是数组那么解析的就是多个,如果是单行解析的就是1
	expectedArgsCount int
	//数据包的类型。例如:*、+、$ 等数据包的类型,参考resp协议说明
	msgType byte
	//传递的参数本身,二维数据进行数据的存储,例如:set k v,就有三个组
	args [][]byte
	//解析后续需要解析数据的字节长度
	bulkLen int64
}

// finished 当前解析器是否已经解析完成
func (r *readState) finished() bool {
	//解析出来的个数跟参数的个数一样
	return r.expectedArgsCount > 0 && len(r.args) == r.expectedArgsCount
}

// result 根据指令的类型保存结果返回出去
func (r *readState) result() resp.Reply {
	switch r.msgType {
	case common.ASTERISK:
		return reply.MakeMultiBulkReply(r.args)
	case common.DOLLAR:
		return reply.MakeBulkReply(r.args[0])
	default:
		return nil
	}
}

// parser0 解析数据 tcp发送的数据就是 io.Reader
func parser0(reader io.Reader, ch chan *Payload) {
	//就算出现错误了也不能跳出死循环
	defer func() {
		if err := recover(); err != nil {
			logger.Error(string(debug.Stack()))
		}
	}()
	bufReader := bufio.NewReader(reader)
	state := &readState{}
	var err error
	var msg []byte
	//死循环不停的读取数据
	for true {
		var ioErr bool
		/**
		msg:返回读取\n为分割的一行数据,ioErr:代表是否读取到异常,err:返回的异常
		例如:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n 这样的数据,那么读取的就是 *3\r\n
		*/
		msg, ioErr, err = readLine(bufReader, state)
		if err != nil { //先读一行数据,判断是否是io错误
			if ioErr {
				//写入错误数据到管道中
				ch <- &Payload{
					Err: err,
				}
				close(ch)
				return
			}
			//普通的错误
			ch <- &Payload{
				Err: err,
			}
			state = &readState{}
			continue
		}
		/**
			判断是否是多行解析,默认第一次进来都是false,里面读取了之后将会将其状态进行改变
			例如:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
			第一次解析一行:*3\r\n 改变状态 readingMultiline 为多行状态,解析 3 表示数组有3个参数
		    第二次循环解析一行:$3\r\n 表示后面是个字符串,并且第一次将 readingMultiline 的状态进行了改变所以走到 else中通过 readBody() 进行读取,修改 state.bulkLen说明后续的参数字节长度
			第三次循环解析一行:SET\r\n 解析的是参数,通过 readBody() 进行解析,将数据直接存入到 state.args[][] 数组中
			第四次循环解析一行:$3\r\n 继续解析后续参数的长度
			第五次循环解析一行:KEY\r\n 读取的参数存入到数组中
			第六次循环解析一行:$5\r\n 继续解析后续参数的长度为5
			第七次循环解析一行:VALUE\r\n 读取的参数存入到数组中
		*/
		if !state.readingMultiline {
			//判断数据第一个符号是什么类型
			first := msg[0]
			//通过 adjustType() 函数判断是什么类型的数据,然后调用返回的函数
			payload := adjustType(first)(msg, state, ch)
			//将响应的实体返回给管道中
			if payload != nil {
				ch <- payload
			}
		} else {
			//读取数据
			err := readBody(msg, state)
			if err != nil {
				//如果err不为空,返回一个协议错误
				ch <- &Payload{
					Err: errors.New("protocol error:" + string(msg)),
				}
				//重置状态器
				state = &readState{}
				continue
			}
			//判断是否读取完成了
			if state.finished() {
				ch <- &Payload{
					//包装一下Resp对象
					Data: state.result(),
				}
				state = &readState{}
			}
		}
	}
}

//adjustType 判断类型返回对应类型处理的函数
func adjustType(first byte) func(msg []byte, state *readState, ch chan *Payload) *Payload {
	//返回一个默认的处理函数,打印一下错误信息
	resultFunc := func(msg []byte, state *readState, ch chan *Payload) *Payload {
		logger.Error("不支持当前类型:%s 的数据包处理", first)
		return nil
	}
	//先判断 * 号
	if first == common.ASTERISK {
		//协议解析错误
		resultFunc = func(msg []byte, state *readState, ch chan *Payload) *Payload {
			// * 号解析多行数据
			err := parseMultiBulkHeader(msg, state)
			//处理读取错误
			if err != nil {
				state = &readState{}
				return &Payload{
					Err: errors.New("protocol error:" + string(msg)),
				}
			}
			//如果解析的数组长度为空的
			if state.expectedArgsCount <= 0 {
				state = &readState{}
				return &Payload{
					//给redis的核心响应一个空的数组而不是给客户端返回
					Data: reply.MakeEmptyMultiBulkReply(),
				}
			}
			return nil
		}
	} else if first == common.DOLLAR { //解析 $
		resultFunc = func(msg []byte, state *readState, ch chan *Payload) *Payload {
			err := parseBulkHeader(msg, state)
			//处理读取错误
			if err != nil {
				state = &readState{}
				return &Payload{
					Err: errors.New("protocol error:" + string(msg)),
				}
			}
			//空字符串的响应 -1代表空
			if state.bulkLen == -1 {
				state = &readState{}
				return &Payload{
					//给redis的核心响应一个空的数组而不是给客户端返回
					Data: reply.MakeNullBulkReply(),
				}
			}
			return nil
		}
	} else { //解析 +或者-号
		resultFunc = func(msg []byte, state *readState, ch chan *Payload) *Payload {
			//解析单行
			result, err := parseSingleLine(msg)
			//将状态器清空一下
			state = &readState{}
			return &Payload{
				Data: result,
				Err:  err,
			}
		}
	}
	return resultFunc
}

// ParseStream 解析字节流的时候会返回一个管道,解析完成之后发送数据到管道中
func ParseStream(reader io.Reader) chan *Payload {
	//创建一个管道
	ch := make(chan *Payload)
	go parser0(reader, ch)
	//给redis核心响应一个ch管道,核心层只需要监听这个管道数据即可
	return ch
}

// readLine 根据指示读取一行数据。例如:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
func readLine(bufReader *bufio.Reader, state *readState) ([]byte, bool, error) {
	/**
	1. 按照\r\n的正常情况进行分割(没有读取到$字符),因为 $ 是预设指令,指示后面需要读取几个字符
	2. 如果之前读取到 $字符,严格按照$后面跟随的字符个数进行读取,不能进行分行
	*/
	var msg []byte
	var err error
	if state.bulkLen == 0 { // 1.表示前面没有读取到 $等预设指令 按照\r\n切分
		msg, err = bufReader.ReadBytes('\n')
		if err != nil {
			return nil, true, err
		}
		//判断 \n切了之后 倒数第二个是否是 \r进行区分,就是协议格式不对
		if len(msg) == 0 || msg[len(msg)-2] != '\r' {
			return nil, false, errors.New("protocol error: " + string(msg))
		}
	} else {
		//2. 前面读取到了 $ 等预设字符, state.bulkLen + 2 需要将后面的\r\n也要读取进来
		msg = make([]byte, state.bulkLen+2)
		//将bufReader中根据数组的长度全部读取出来
		_, err := io.ReadFull(bufReader, msg)
		if err != nil {
			return nil, true, err
		}
		//判断是否是 \r\n进行区分,就是协议格式不对
		if len(msg) == 0 || msg[len(msg)-2] != '\r' || msg[len(msg)-1] != '\n' {
			return nil, false, errors.New("protocol error: " + string(msg))
		}
		//将预设的长度设置为0
		state.bulkLen = 0
	}
	return msg, false, nil
}

// parseMultiBulkHeader 解析多行,例如:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
func parseMultiBulkHeader(msg []byte, state *readState) error {
	var err error
	var expectedLine uint64
	//将 *3\r\n 中的3给切出来,获取到当前数据包的长度
	expectedLine, err = strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)
	if err != nil {
		return errors.New("protocol error: " + string(msg))
	}
	if expectedLine == 0 { //读取出来的是空的数据
		state.expectedArgsCount = 0
		return nil
	} else if expectedLine > 0 { //表示后续有数据的长度
		//解析的标志位,消息的类型
		state.msgType = msg[0]
		//现在正在读多行状态
		state.readingMultiline = true
		//设置后续参数的个数
		state.expectedArgsCount = int(expectedLine)
		//初始化数组的长度,二维数组进行存储
		state.args = make([][]byte, 0, expectedLine)
		return nil
	} else {
		return errors.New("protocol error: " + string(msg))
	}
}

// parseBulkHeader 解析单行,例如:$4\r\n
func parseBulkHeader(msg []byte, state *readState) error {
	var err error
	//解析单行的数据长度例如:$4\r\n,这里解析出来就是4
	state.bulkLen, err = strconv.ParseInt(string(msg[1:len(msg)-2]), 10, 32)
	if err != nil {
		return errors.New("protocol error: " + string(msg))
	}
	if state.bulkLen == -1 {
		return nil
	} else if state.bulkLen > 0 {
		//解析的标志位,消息的类型
		state.msgType = msg[0]
		//现在正在读多行状态
		state.readingMultiline = false
		//读取参数的行数为1行
		state.expectedArgsCount = 1
		//初始化数组的长度,单行的话那么后续的参数就为1
		state.args = make([][]byte, 0, 1)
		return nil
	} else {
		return errors.New("protocol error: " + string(msg))
	}
}

// 解析客户端响应的 +OK,-err, :5\r\n 格式,这种就可以直接解析完
func parseSingleLine(msg []byte) (resp.Reply, error) {
	//将正文解析出来
	str := strings.TrimSuffix(string(msg), "\r\n")
	var result resp.Reply
	switch msg[0] {
	case common.PLUS: //解析正确响应包
		result = reply.MakeStatusReply(str[1:])
	case common.DASH: //解析错误响应包
		result = reply.MakeStandardErrReply(str[1:])
	case common.COLON: //解析整数数据包
		val, err := strconv.ParseInt(str[1:], 10, 64)
		if err != nil {
			return nil, errors.New("protocol error: " + string(msg))
		}
		result = reply.MakeIntReply(val)
	}
	return result, nil
}

/**
可能是以下两种情况:
1. $3\r\n
2. SET\r\n
*/
func readBody(msg []byte, state *readState) error {
	//这里面是将后续的 \r\n的分隔符截取了
	line := msg[0 : len(msg)-2]
	var err error
	// $3 解析$后面的3出来
	if line[0] == common.DOLLAR {
		//保存后续需要解析的字节长度到状态器里面
		state.bulkLen, err = strconv.ParseInt(string(line[1:]), 10, 64)
		if err != nil {
			return errors.New("protocol error: " + string(msg))
		}
		//如果情况类型是 $0\r\n
		if state.bulkLen <= 0 {
			//空的长度
			state.args = append(state.args, []byte{})
			state.bulkLen = 0
		}
	} else {
		//走到这里数据就是 SET
		state.args = append(state.args, line)
	}
	return nil
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值