一,RESP协议
1.1 介绍
Redis是一个CS架构的软件,通信一般分两步(不包括pipeline和Pubsub)
-
客户端(client)向服务端(server)发送一条命令
-
服务端解析并执行命令,返回响应结果给客户端
因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议。
而在Redis中采用的是RESP(Redis Serialization Protocol)协议:
- Redis 1.2版本引入了RESP协议
- Redis 2.0版本中成为与Redis服务端通信的标准,称为RESP2
- Redis6.0版本中,从RESP2升级到了RESP3协议,增加了更多数据类型并且支持6.0的新特性--客户端缓存
但目前,默认使用的依然是RESP2协议,也是我们要学习的协议版本(以下简称RESP)
1.2 数据类型
在RESP中,通过首字节的字符来区分不同数据类型,常用的数据类型包括5种:
-
单行字符串:首字节是
+,后面跟上单行字符串,以CRLF(\r\n)结尾。例如返回"OK"就是"+OK\r\n" -
错误(Errors):首字节是
-,与单行字符串格式一样,只是字符串是异常信息,例如:-Error message\r\n -
数值:首字节是
:,后面跟上数字格式的字符串,以CRLF结尾。例如::10\r\n -
多行字符串:首字节是
$,表示二进制安全的字符串,最大支持512MB- 如果大小为0,则代表空字符串:
$0\r\n\r\n - 如果大小为-1,则代表不存在:
$-1\r\n
- 如果大小为0,则代表空字符串:
-
数组:首字节是
*,后面跟上数组元素个数,再跟上元素,数据类型不限
二,模拟Redis客户端
package com;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
public class Main {
static Socket socket;
static PrintWriter writer;
static BufferedReader reader;;
public static void main(String[] args) {
try{
//1.建立连接
String host="42.192.219.41";
int port=6379;
socket=new Socket(host,port);
//2.获取输入流,输出流
writer = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(),
StandardCharsets.UTF_8
)
);
reader=new BufferedReader(
new InputStreamReader(socket.getInputStream(),
StandardCharsets.UTF_8
)
);
//3.发起请求 set name 张三
sendRequest("auth","yz2763000");
sendRequest("set","name","张三");
//4.解析响应
Object obj=handleResponse();
System.out.println("obj="+obj);
}catch(IOException e){
e.printStackTrace();
}finally{
//5。释放连接
try {
if(writer != null){
writer.close();
}
if(reader != null) {
reader.close();
}
if(socket != null){
socket.close();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
;
}
private static Object handleResponse() throws IOException {
//1.读取首字节,判断数据类型标示
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("unknown prefix:"+prefix);
}
}
private static Object readBulkString() throws IOException {
//1.获取数组大小
int len =Integer.parseInt(reader.readLine());
if(len <=0){
return null;
}
//2.遍历,依次读取每个元素
//接受多个集合
List<Object> list = new ArrayList<>();
for (int i = 0; i < len; i++) {
//每个元素都是完整的数据
list.add(handleResponse());
}
return list;
}
//set name 张三
private static void sendRequest(String ...args) {
writer.println("*"+args.length);
for (String arg : args) {
writer.println("$"+arg.getBytes(StandardCharsets.UTF_8).length);
writer.println(arg);
}
writer.flush();
}
}