| 虚拟主机域名注册-常见问题 → 服务器知识 → 服务器问题 | ||||
几乎所有的主流编程语言都有 Redis的客户端(http://redis.io/clients),不考虑Redis 非常流行的原因,如果站在技术的角度看原因还有两个: 客户端与服务端之间的通信协议是在 TCP 协议之上构建的。 Redis制定了RESP(REdis Serialization Protocol,Redis序列化协议)实现客户端与服务端的正常交互,这种协议简单高效,既能够被机器解析,又容易被人类识别。 例如客户端发送一条set hello world命令给服务端,按照RESP 的标准,客户端需要将其封装为如下格式(每行用\r\n分隔): *3 $3 SET $5 hello $5 world 这样 Redis服务端能够按照RESP将其解析为set hello world命令,执行后回复的格式如下: +OK 可以看到除了命令(sethelloworld)和返回结果(0K)本身还包含了一些特殊字符以及数字,下面将对这些格式进行说明。 3.1 发送命令格式 RESP的规定一条命令的格式如下,CRLF代表 "\r\n" : *<参数数量> CRLF $<参数1的字节数量> CRLF <参数 1> CRLF ... $<参数的字节数量>CRLF <参数 N> CRLF 依然以 set hell world 这条命令进行说明。参数数量为3个,因此第一行为: *3 参数字节数分别是 3 5 5,因此后面几行为: $3 SET $5 hello $5 world 有一点要注意的是,上面只是格式化显示的结果,实际传输格式为如下代码,整个过程如图所示: *3\r\n$3\r\nSET\r\n$5\r\nhello\r\n$5\r\nworld\r\n 3.2.返回结果格式 Redis 的返回结果类型分为以下五种,如图所示: 状态回复:在 RESP 中第一个字节为"+"。 错误回复:在RESP 中第一个字节为"-"。 整数回复:在 RESP 中第一个字节为":"。 字符串回复:在 RESP 中第一个字节为"$"。 多条字符串回复:在RESP 中第一个字节为"*"。 我们知道 redis-c1i只能看到最终的执行结果,那是因为redis-cli本身就是按照RESP进行结果解析的,所以看不到中间结果,redis-cli.c源码对命令结果的解析结构如下: static sds cliFormatReplyTTY(redisReply *r,char *pre@x){ sds out=sdsempty(); switch(r->type)( Case REDIS REPLY_ERROR: // 处理错误回复 Case REDIS REPLY STATUS: // 处理状态回复 Case REDIS REPLY_INTEGER: // 处理整数回复 Case REDIS REPLY_STRING: //处理字符串回复 Case REDIS_REPLY_NIL: //处理空 Case REDIS_REPLY_ARRAY: //处理多条字符串回复 return out; } 例如执行 set hello world,返回结果是 0K,并不能看到加号: 127.0.0.1:6379> set hello world OK 为了看到Reds服务端返回的"真正"结果,可以使用nc命令、telnet 命令、甚写一个socket 程序进行模拟。下面以nc命令进行演示,首先使用nc 127.0.0.6379连接到 Redis: nc 127.0.0.1 6379 状态回复:set hello world的返回结果为 + OK: sh复制代码 set hello world +OK 错误回复:由于sethx这条命令不存在,那么返回结果就是"-"号加上错误消息 sethx -ERR unknown command 'sethx' 整数回复:当命令的执行结果是整数时,返回结果就是整数回复,例如incr、exists、del、dbsize返回结果都是整数,例如执行 incr counter 返回结果就是 ":" 加上整数: incr counter :1 字符串回复:当命令的执行结果是字符串时,返回结果就是字符串回复。例如get、hget 返回结果都是字符串,例如get hello 的结果为 "$5\r\nworld\r\n": get hello $5 world 多条字符串回复:当命令的执行结果是多条字符串时,返回结果就是多条字符串回复例如 mget、hgetall、lrange 等命令会返回多个结果,例如下面操作: 首先使用 mset 设置多个键值对: mset java jedis python redis-py +OK 然后执行 mget 命令返回多个结果,第一个 *2 代表返回结果的个数,后面的格式是和字符串回复一致的: mget java python *2 $5 jedis $8 redis-py 有一点需要注意,无论是字符串回复还是多条字符串回复,如果有nil值,那么会返回 $-1。 例如,对一个不存在的键执行 get 操作,返回结果为: get not_exist_key $-1 如果批量操作中包含一条为 nil值的结果,那么返回结果如下: mget hello not_exist_key java *3 $5 world $-1 $5 jedis 有了 RESP 提供的发送命令和返回结果的协议格式,各种编程语言就可以利用其来实现相应的 Redis 客户端。 3.3 Redis通信协议-基于Socket自定义Redis的客户端 Redis支持TCP通信,因此我们可以使用Socket来模拟客户端,与Redis服务端建立连接: public class Main { static Socket s; static PrintWriter writer; static BufferedReader reader; public static void main(String[] args) { try { // 1.建立连接 String host = "192.168.150.101"; int port = 6379; s = new Socket(host, port); // 2.获取输出流、输入流 writer = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8)); reader = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8)); // 3.发出请求 // 3.1.获取授权 auth 123456 sendRequest("auth", "1232456"); Object obj = handleResponse(); System.out.println("obj = " + obj); // 3.2.set name hello sendRequest("set", "name", "hello"); // 4.解析响应 obj = handleResponse(); System.out.println("obj = " + obj); // 3.2.set name hello sendRequest("get", "name"); // 4.解析响应 obj = handleResponse(); System.out.println("obj = " + obj); // 3.2.set name hello sendRequest("mget", "name", "num", "msg"); // 4.解析响应 obj = handleResponse(); System.out.println("obj = " + obj); } catch (IOException e) { e.printStackTrace(); } finally { // 5.释放连接 try { if (reader != null) reader.close(); if (writer != null) writer.close(); if (s != null) s.close(); } catch (IOException e) { e.printStackTrace(); } } } private static Object handleResponse() throws IOException { // 读取首字节 int prefix = reader.read(); // 判断数据类型标示 switch (prefix) { case '+': // 单行字符串,直接读一行 return reader.readLine(); case '-': // 异常,也读一行 throw new RuntimeException(reader.readLine()); case ':': // 数字 return Long.parseLong(reader.readLine()); case '$': // 多行字符串 // 先读长度 int len = Integer.parseInt(reader.readLine()); if (len == -1) { return null; } if (len == 0) { return ""; } // 再读数据,读len个字节。我们假设没有特殊字符,所以读一行(简化) return reader.readLine(); case '*': return readBulkString(); default: throw new RuntimeException("错误的数据格式!"); } } private static Object readBulkString() throws IOException { // 获取数组大小 int len = Integer.parseInt(reader.readLine()); if (len <= 0) { return null; } // 定义集合,接收多个元素 List
|
||||
| >> 相关文章 | ||||
| 没有相关文章。 | ||||






