「黑马 Redis 原理」三、通信协议
RESP 协议
Redis 是一个 CS 架构的软件,通信一般分两步(不包括 pipeline 和 PubSub)
- 客户端 client 向服务端 server 发送一条命令
- 服务端解析并执行命令,返回响应结果给客户端
因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议
RESP:redis serialization protocol
学习 RESP2 协议,在 2.0 版本推出,6.0 升级为 RESP3 但因为变化过大仍默认使用 RESP2 协议
RESP 协议数据类型
对于单行字符串、错误、数据都是二进制不安全的,如果在内容中穿插了 \r\n 可能会误解成结尾
数组类型看上去其实和 AOF 文件挺像

image.png|500
模拟 Redis 客户端
跑下来 reader 收不到信息?
public class Main {
static Socket s;
static PrintWriter out;
static BufferedReader in;
public static void main(String[] args) {
try {
// 1. 建立连接
String host = "redis.orb.local";
int port = 6379;
s = new Socket(host, port);
// 2. 获取输入、输出流
out = new PrintWriter(
new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8));
in = new BufferedReader(new InputStreamReader(s.getInputStream()));
// 3. 发出请求并解析
// 获取授权 auth TheBestWorkLoanapp1 sendRequest("auth", "TheBestWorkLoanapp1");
Object obj = handleResponse();
System.out.println(obj.toString());
// 设置键值对 set name Chanler sendRequest("set", "name", "Chanler");
obj = handleResponse();
System.out.println(obj.toString());
// 获取键值对 get name sendRequest("get", "name");
obj = handleResponse();
System.out.println(obj.toString());
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 释放链接
try {
if (s != null) {s.close();}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (out != null) {out.close();}
} catch (Exception e) {
e.printStackTrace();
}
try {
if (in != null) {in.close();}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static Object handleResponse() throws IOException {
// 读取首字节判断数据类型
int prefix = in.read();
switch (prefix) {
case '+':
return in.readLine();
case '-':
throw new RuntimeException(in.readLine());
case ':':
return Long.parseLong(in.readLine());
case '$':
int len = Integer.parseInt(in.readLine());
if (len == -1) {
return null;
}
if (len == 0) {
return "";
}
// 这里应该读 len 个字节,但这里已经转换成了字符流,所以假设不存在特殊字符
return in.readLine();
case '*':
return readBulkString();
default:
throw new RuntimeException("Invalid prefix");
}
}
private static Object readBulkString() throws IOException {
int len = Integer.parseInt(in.readLine());
if (len == 0) {
return null;
}
List<Object> list = new ArrayList<>();
for (int i=0; i<len; i++) {
list.add(handleResponse());
}
return list;
}
// 发送 set name Chanler
private static void sendRequest(String ... args) {
// \r\n 就是换行符,这里用 println 直接附带了
out.println("*" + args.length);
for (String arg : args) {
out.println("$" + arg.getBytes(StandardCharsets.UTF_8).length);
out.println(arg);
}
out.flush();
}
}