go redis 协议篇

1 Redis网络协议详解

redis的网路协议全名是Redis Serialization Protocol (RESP), 它设计五项,如下所示:

  • 正常回复
  • 错误回复
  • 整数
  • 多行字符串
  • 数组

(1)正常回复
正常回复以"+"开头,以"\r\n"结尾的字符串形式
(2) 错误回复
错误回复以"-" 开头,以"\r\n" 结尾的字符串形式
(3) 整数
整数以":"开头,以"\r\n"结尾的字符串形式
(4) 多行字符串
多行字符串以""开头,后跟实际发送字节数,以"\r\n"结尾。 举例比如hello.world,对应:11\r\nhello.world\r\n, 首先11代表hello.world的长度11个字节,后面紧跟\r\n, 再然后就是数据内容,最后再紧跟\r\n
为什么要在字符串长度后面跟一个\r\n ?
假设你需要处理的是空字符串 "" , 那么就是0\r\n\r\n 假设你需要处理的是hello\r\nworld, 那么就是14\r\nhello\r\nworld\r\n
(5)数组 多个多行字符串
以""开头,后跟成员个数
举例: SET key value
3\r\n3\r\n**SET**\r\n3\r\n
key\r\n5\r\n**value**\r\n *后的3代表有3个成员,然后分别是第一个SET:3\r\nSET\r\n, 在接着是第2个key: 3\r\n**key**\r\n,最后是第三个value:5\r\nvalue**\r\n

2 实现常见的几种正常Reply函数

首先上面分析了redis的协议,也了解了一些数据的回复,这里实现一些常见的一些Reply。
接口定义Reply:

package resp

// Reply 代表服务端对客户端的回复
type Reply interface {
    // ToBytes 因为tcp协议里面的读写都是针对字节的,所以这里都要转为字节
    ToBytes() []byte
}

实现PongReply:

package reply

// PongReply redis客户端输入一个PING Server端返回一个PONG
type PongReply struct {
}

var pongBytes = []byte("+PONG\r\n")

func (p *PongReply) ToBytes() []byte {

    return pongBytes
}

func MakePongReply() *PongReply {
    return &PongReply{}
}

实现OkReply:

type OkReply struct {
}

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

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

// 这样无需每次make的时候都去创建OkReply对象
var okReply = &OkReply{}

func MakeOkReply() *OkReply {
    return okReply
}

实现空字符串Reply:是NULL的空,不是""的空

// NullBulkReply 服务端的空回复
type NullBulkReply struct {
}

var nullBulkReply = []byte("$-1\r\n")

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

func MakeNullBulkReply() *NullBulkReply {
    return &NullBulkReply{}
}

实现空数组的Reply:

// EmptyMultiBulkReply 服务端空数组的回复
type EmptyMultiBulkReply struct {
}

var emptyMultiBulkReplyBytes = []byte("*0\r\n")

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

func MakeEmptyMultiBulkReply() *EmptyMultiBulkReply {
    return &EmptyMultiBulkReply{}
}

实现NoReply:

type NoReply struct {
}

var noReplyBytes = []byte("")

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

3 实现常见的几种错误Reply函数

  • UnknownErrReply 未知错误
 // UnknownErrReply 未知错误
  type UnknownErrReply struct {
  }
  var unKnownErrBytes = []byte("-Err unknown\r\n")

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

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

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

  • 参数个数异常,比如SET KEY VALUE 需要三个参数,但是只传2个,则参数个数有问题
// ArgNumErrReply 参数个数异常,比如SET KEY VALUE 需要三个参数,但是只传2个,则参数个数有问题
type ArgNumErrReply struct {
    Cmd string // 记录那个客户端的指令
}

func (a *ArgNumErrReply) Error() string {
    return "Err wrong number of arguments for '" + a.Cmd + "' command\r\n"
}

func (a *ArgNumErrReply) ToBytes() []byte {
    return []byte("-Err wrong number of arguments for '" + a.Cmd + "' command\r\n")
}

func MakeArgNumErrReply(cmd string) *ArgNumErrReply {
    return &ArgNumErrReply{
        Cmd: cmd,
    }
}
  • 语法错误
// SyntaxErrReply 语法错误
type SyntaxErrReply struct {
}

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

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

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

var syntaxErrReply = &SyntaxErrReply{}

func MakeSyntaxErrReply() *SyntaxErrReply {
    return syntaxErrReply
}
  • 数据类型错误
// WrongTypeErrReply 数据类型错误
type WrongTypeErrReply struct {
}

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

func (w *WrongTypeErrReply) Error() string {
 return "wrong type operation against a key holding the wrong kind of value"
}

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

var wrongTypeErrRepley = new(WrongTypeErrReply)

func MakeWrongTypeErrorReply() *WrongTypeErrReply {
 return wrongTypeErrRepley
}

  • 协议错误

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

func (p *ProtocolErrReply) Error() string {
    return "Err protocol error: " + p.Msg
}

func (p *ProtocolErrReply) ToBytes() []byte {
    return []byte("-Err protocol error: '" + p.Msg + "'\r\n")
}

func MakeProtocolErrReply(msg string) *ProtocolErrReply {

    return &ProtocolErrReply{
        Msg: msg,
    }

}

4 自定义Reply函数

首先第一节讲了5中协议的使用情况,这里针对这5中情况进行自定义Reply实现:
redis的网路协议全名是Redis Serialization Protocol (RESP), 它设计五项,如下所示:

  • 正常回复
  • 错误回复
  • 整数
  • 多行字符串
  • 数组

(1)正常回复
正常回复以"+"开头,以"\r\n"结尾的字符串形式

// 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,
    }
}

(2) 错误回复
错误回复以"-" 开头,以"\r\n" 结尾的字符串形式

// ErrorReply 定义redis错误的回复接口
type ErrorReply interface {
    Error() string
    ToBytes() []byte
}

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

func (s *StandardErrReply) Error() string {
    return s.Status
}

func (s *StandardErrReply) ToBytes() []byte {

    return []byte("-" + s.Status + CRLF)
}

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

(3) 整数
整数以":"开头,以"\r\n"结尾的字符串形式

// 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,
    }

}

(4) 多行字符串
多行字符串以""开头,后跟实际发送字节数,以"\r\n"结尾。 举例比如hello.world,对应:11\r\nhello.world\r\n, 首先11代表hello.world的长度11个字节,后面紧跟\r\n, 再然后就是数据内容,最后再紧跟\r\n
为什么要在字符串长度后面跟一个\r\n ?
假设你需要处理的是空字符串 "" , 那么就是0\r\n\r\n 假设你需要处理的是hello\r\nworld, 那么就是14\r\nhello\r\nworld\r\n

var (
    nullBulkReply = "$-1"
    CRLF          = "\r\n"
)

// BulkReply  自定义字符串的Reply
type BulkReply struct {
    // 字符串的值,比如hello, 那么reply 就是$5hello\r\n,相当于自动转换这个过程
    Arg []byte
}

func (b *BulkReply) ToBytes() []byte {
    if len(b.Arg) == 0 {
        return []byte(nullBulkReply + CRLF)
    }
    return []byte("$" + strconv.Itoa(len(b.Arg)) + CRLF + string(b.Arg) + CRLF)
}

// MakeBulkReply 外面传入一个字符串,自动拼装返回值
func MakeBulkReply(arg string) *BulkReply {
    return &BulkReply{
        Arg: []byte(arg),
    }
}

(5)数组 多个多行字符串
以""开头,后跟成员个数
举例: SET key value
3\r\n3\r\n**SET**\r\n3\r\n
key\r\n5\r\n**value**\r\n *后的3代表有3个成员,然后分别是第一个SET:3\r\nSET\r\n, 在接着是第2个key: 3\r\n**key**\r\n,最后是第三个value:5\r\nvalue**\r\n

// MultiBulkReply 多字符串的自定义封装
type MultiBulkReply struct {
    Args [][]byte
}

func (m *MultiBulkReply) ToBytes() []byte {
    argNum := len(m.Args)
    var buf bytes.Buffer
    buf.WriteString("*" + strconv.Itoa(argNum) + CRLF)

    for _, arg := range m.Args {
        if arg == nil {
            buf.WriteString(nullBulkReply + CRLF)
        } else {
            buf.WriteString("$" + strconv.Itoa(len(arg)) + CRLF + string(arg) + CRLF)
        }
    }
    return buf.Bytes()
}

func MakeMultiBulkReply(args [][]byte) *MultiBulkReply {

    return &MultiBulkReply{
        Args: args,
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 前言 Redis客户端使用被称为RESP(Redis序列化协议)的协议与Redis服务器进行通讯。虽然该协议是专门...
    pyihe阅读 692评论 0 1
  • Redis是用单线程来处理多个客户端的访问,因此作为Redis的开发和运维人员需要了解Redis服务端和客户端的通...
    linuxzw阅读 902评论 0 5
  • Redis通信协议 Redis 协议在以下三个目标之间进行折中: 易于实现 可以高效地被计算机分析(parse) ...
    蜗牛淋雨阅读 753评论 0 2
  • 几乎所有的主流编程语言都有Redis的客户端(http://redis.io/clients),不考虑Redis非...
    handsomemao666阅读 895评论 0 0
  • # 前言 ### 为什么我要尝试写作技术书籍 - 一个人年轻时经历的艰难会在未来成为他的财富 # 第一篇 基础和应...
    zhzosh阅读 608评论 0 0