最后更新:2021-11-21 14:31:22 手机定位技术交流文章
由Flickr用户pic.
Redis客户端(redis-cli)和服务提供商(redis-server)通信(redis-cli)基于TCP连接,它们之间的数据传输编码被称为 redis通信协议。 只要我们的 redis-cli完成了该协议的解析和编码,我们就可以完成所有重置活动。
Rediis 的用意是非常易读和简单易用。 请参考特定 redis 通信协议的通讯协议( 协议) 。 然后我们会通过, 并告诉你在进行交易的过程中我们在做什么 。
一. 建立 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 }
我们可以在发送和接收数据时使用它们。Read () 和 conn. write () 。
2. 发送请求
完成此请求的首个字节为“ * ”, 中间是包括命令本身在内的参数数, 之后是“ rn” 。 在此之后, 请使用“ $” 加上数字段数, 并同时以“ rn” 结尾。 如果您执行 SET 关键 Liangwt 客户端的请求, 则“ * 3rnSETERnKeyernlnlangwtrn” 。
注意:
为了测试它,我们可以使用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看到请求协议是极其基本的,因此对请求协议的执行没有更多的解释,因此立即插入代码(以下以字符串咒语为基础,仅用于更直观的演示,效率不高,我们在实际代码中使用字节。Buffer会这样做 ) 。
我不确定, Func MultiBulkMarshal. 对不起, 我很抱歉, 但是我很抱歉, 但是我很抱歉。 Itoa( len( args) ) s "rn" // 命令所有参数, v: = 范围 angs s + = "$" sstrconf. Itoa( len(v)) s + = "rn"
在编码的订单和参数完成后,我们可以继续到 Conn.Write (), 它将数据推到服务器 。
func main() { // .... req := MultiBulkMarshal("SET", "key", "liangwt") _, err = conn.Write([]byte(req)) if err != nil { log.Fatal(err) } // to be continue }
3. 获取回复
我们要做的第一件事就是使用 conn.Read () 从 tcp 获得服务端响应。
func main() { // .... p := make([]byte, 1024) _, err = conn.Read(p) if err != nil { log.Fatal(err) } // to be continue }
4. 解析回复
一旦我们拿到了 p. Redis服务的反应是在不同的情况下。
前四类是一组的,因为它们都是单型返回。
我们认为,最后一批多批答复属于不同类别,因为它们包含以前类型的混合,如你所见,它们与我们的要求完全相同。
基于上述考虑,我们发展了两种职能,一种是解释单一类型,另一种是独立解释混合类型,这样,在区分特定类型的混合类型时,我们只需将一种职能称为单一类型。
在解构具体协定之前,我们应完成一项功能,其内容如下:
func ReadLine(p []byte) ([]byte, error) { for i := 0; i
第一种状态回复:
状态响应是“ 以“ +” 开头的“ rn” 结尾处的单行字符串。如果 SET 命令成功,则返回以下值:“ +Korn” 。
所以我们检查第一个字符是否等于“+”,如果是,我们读到。
func SingleUnMarshal(p []byte) ([]byte, int, error) { var ( result []byte err error length int ) switch p[0] { case '+': result, err = ReadLine(p[1:]) length = len(result) + 3 } return result, length, err }
注:除了归还答复的实际内容外,我们还要归还整个答复的长度,以便在随后处理大量批次答复时帮助找到下一个解答。
第二种错误回复:
错误回复的首个字节是“ - ”, 在“ rn” 结尾处的单行字符串。 如果缺少 SET 密钥, 返回值 : “ - ERR 错误的“ set” 命令参数数 ”
错误的反应与国家的反应相当相似,补救措施也是一样的。 我们只需要增加一个案例。
func SingleUnMarshal(p []byte) ([]byte, int, error) { var ( result []byte err error length int ) switch p[0] { case '+', '-': result, err = ReadLine(p[1:]) length = len(result) + 3 } return result, length, err }
第三种整数回复:
当运行 LLEN Mylist 命令时, 整数回答的第一个字节是“ : ”, 中间是字符串的整数, 字符串“ rn”. returns”: 10n结尾处的单行文字 。
整数回答与前两个相同,但返回字符串的十进制整数。
func SingleUnMarshal(p []byte) ([]byte, int, error) { var ( result []byte err error length int ) switch p[0] { case '+', '-', ':': result, err = ReadLine(p[1:]) length = len(result) + 3 } return result, length, err }
第四种批量回复:
批次回复的第一个字节是“$”,然后是字符串中指定真实回答长度的号码,然后是“rn”,然后是实际回复数据,最后是另一个“rn”。
因此,对分批处理的处理已经完成:
然而,对于我不存在的密钥,批量响应使用特殊值 -1 作为回复的长度, 而我们不需要继续读取此点的实际响应。 例如, 获得 NET_ EXIST_ KEY 返回值 : “ 1 ”, 因此我们必须通过允许函数返回空对象( 无) 而不是空值来决定此方案 。
n, 错误: = 读取线( p[ 1 ) 如果错误! = 无返回 [ 字节, 0, 如果 l= - 1 返回零, 0, 零 / +3 $r n 3 字符结果 =
思考:
为何在通知前重新使用字节号,然后按照设定的长度阅读,而不是仅仅读完第二行,直到 rn?
原因显而易见:这种方法允许在与具体的返回内容无关的情况下重新阅读返回值,在行读数方面,使用任何分隔符都可能导致在内容解析时与内容的分隔符重新分离,导致分辨率错误。
考虑一下: 我们 SET 键“ liangrnwt ”, 因此当我们检索密钥时, 服务器返回值“ $9 rnlanngrnwtn ”, 完全忽略了该值的影响 。
第五次多容量反应:
多批次答复是多个答复的阵列,第一个字节为“*”,后面是字符串的整数值,记录多个批次答复中包含的答复数量,后面是“rn”。如果 LRANSNGE My list 0-2 给值 : “()rnn”
因此,需要额外批次答复,以纠正问题:
在这里,我们使用在单行处理后返回的字节长度,这样很容易确定下一行从何处开始。
在处理大量批量答复时,应铭记两点:
首先,许多批次回答可能是空白的(空的) 。 例如, 执行 LARENGE NOT_ EXIST_ KEY 0-1 服务端返回值“ rn ” 。 在此点, 客户端会发送空数组字节 。
第二,许多批次回答可能是空的( null multbulk 回复 ) 。 例如, 执行 BLPOP 键键 1 服务端响应值“ *- (rn) ” 。 此时客户应该返回零 。
如果p[0]!(==xx
5. 命令行模式
无障碍的 redis- cli 具有内在的互动性, 用户输入命令然后返回值。 我们可以使用下面的代码在 Go 实现类似的互动命令行 。
func main() { // .... for { fmt.Printf("%s:%d>", host, port) bio := bufio.NewReader(os.Stdin) input, _, err := bio.ReadLine() if err != nil { log.Fatal(err) } fmt.Printf("%sn", input) } }
如果我们执行上面的代码,我们就能完成它。
localhost:6379>set key liang set key liang localhost:6379>get key get key localhost:6379>
发送和解决请求 与我们的redis 将完成rediscli。
func main() { // .... for { fmt.Printf("%s:%d>", host, port) // 获取输入命令和参数 bio := bufio.NewReader(os.Stdin) input, err := bio.ReadString('n') if err != nil { log.Fatal(err) } fields := strings.Fields(input) // 编码发送请求 req := MultiBulkMarshal(fields...) // 发送请求 _, err = conn.Write([]byte(req)) if err != nil { log.Fatal(err) } // 读取返回内容 p := make([]byte, 1024) _, err = conn.Read(p) if err != nil { log.Fatal(err) } // 解析返回内容 if p[0] == '*' { result, err := MultiUnMarsh(p) } else { result, _, err := SingleUnMarshal(p) } } // .... }
6. 总结
到目前为止,我们的 cli 程序已经完全完成, 但是仍然有许多缺陷。 尽管如此, 基本的 redis 协议已经解决了, 我们可以用它连接服务器 。
在我的 Github 库: Simaple redis cli-Rcliet 中可以找到更彻底的redis-cli 执行。
这是文件的全文,预计对任何人都有好处,中文的html将得到更大的支持。
本文由 在线网速测试 整理编辑,转载请注明出处。