使用golang写一个redis-cli的方法示例

2020-01-28 13:38:17于海丽

0. redis通信协议

redis的客户端(redis-cli)和服务端(redis-server)的通信是建立在tcp连接之上, 两者之间数据传输的编码解码方式就是所谓的redis通信协议。所以,只要我们的redis-cli实现了这个协议的解析和编码,那么我们就可以完成所有的redis操作。

redis 协议设计的非常易读,也易于实现,关于具体的redis通信协议请参考:通信协议(protocol)。后面我们在实现这个协议的过程中也会简单重复介绍一下具体实现

1. 建立tcp连接

redis客户端和服务端的通信是建立tcp连接之上,所以第一步自然是先建立连接


package main

import (
 "flag"
 "log"
 "net"
)

var host string
var port string

func init() {
 flag.StringVar(&host, "h", "localhost", "hsot")
 flag.StringVar(&port, "p", "6379", "port")
}

func main() {
 flag.Parse()

 tcpAddr := &net.TCPAddr{IP: net.ParseIP(host), Port: port}
 conn, err := net.DialTCP("tcp", nil, tcpAddr)
 if err != nil {
 log.Println(err)
  }
  defer conn.Close()

 // to be continue
}

后续我们发送和接受数据便都可以使用conn.Read()和conn.Write()来进行了

2. 发送请求

发送请求第一个第一个字节是"*",中间是包含命令本身的参数个数,后面跟着"rn" 。之后使用"$"加参数字节数量并使用"rn"结尾,然后紧跟参数内容同时也使用"rn"结尾。如执行 SET key liangwt 客户端发送的请求为"*3rn$3rnSETrn$3rnkeyrn$7rnliangwtrn"

注意:

    命令本身也作为协议的其中一个参数来发送 rn 对应byte的十进制为 13 10

我们可以使用telnet测试下


wentao@bj:~/github.com/liangwt/redis-cli$ telnet 127.0.0.1 6379
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
*3
$3
SET
$3
key
$7
liangwt
+OK

先暂时忽略服务端的回复,通过telnet我们可以看出请求协议非常简单,所以对于请求协议的实现不做过多的介绍了,直接放代码(如下使用基于字符串拼接,只是为了更直观的演示,效率并不高,实际代码中我们使用bytes.Buffer来实现)


func MultiBulkMarshal(args ...string) string {
 var s string
 s = "*"
 s += strconv.Itoa(len(args))
 s += "rn"

 // 命令所有参数
 for _, v := range args {
 s += "$"
 s += strconv.Itoa(len(v))
 s += "rn"
 s += v
 s += "rn"
 }

 return s
}

在实现了对命令和参数进行编码之后,我们便可以通过conn.Write()把数据推送到服务端


func main() {
  // ....
 req := MultiBulkMarshal("SET", "key", "liangwt")
 _, err = conn.Write([]byte(req))
 if err != nil {
 log.Fatal(err)
 }
 // to be continue
}

3. 获取回复

我们首先实现通过tcp获取服务端返回值,就是上面提到过的conn.Read()。