文章目录
Redis通信协议
RESP协议
Redis是一个CS架构的软件,通信一般分为两步(不包含pipeline和PubSub):
- 客户端(client)向服务端(server)发送一条命令。
- 服务器解析并执行命令,返回响应结果到客户端。
因此,客户端发送命令好服务端响应结果的格式需要有一个规范(否则便无法正常通信),这个规范便是通信协议。
在Redis中采用的是RESP协议:
- Redis1.2版本引入RESP协议。
- Redis2.0版本中称为Redis服务通信的标准,成为REST2。
- Redis6.0版本中,从RESP2升级到RESP3协议,增加了更多数据类型并且支持6.0的新特性(客户端缓存)。
但目前默认使用的依旧是RESP2协议,
数据类型
在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"
-
数组:首字节是 '*****',后面跟上数组元素个数,再跟上元素,元素数据类型不限:
模拟Redis客户端
Redis支持TCP通信,这里边使用Socket模拟客户端与Redis建立连接
java
public static void RedisRequest(String address,String password,String...request) {
Socket socket = null;
PrintWriter writer = null;
BufferedReader reader = null;
try {
// 1. 建立连接
String host = address.substring(0,address.indexOf(":")); // redis的所在ip地址
int port = Integer.parseInt(address.substring(address.indexOf(":") + 1)); //redis的端口号(默认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));
// 获取授权(登录)
if(password!=null && !"".equals(password)){
sendRequest(writer,"auth "+password);
}
// 3. 发出请求 set name xiaoming
sendRequest(writer,request);
// 4. 解析响应(多次解析)
for(int i = 0;i<request.length;i++){
Object obj = handleResponse(reader);
System.out.println(obj);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 5. 释放连接
if(reader !=null){
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(writer!=null){
writer.close();
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
发送一条set命令和一条get命令(set name xiaoming , get name)
java
private static void sendRequest (PrintWriter writer,String...request) {
String[][] req = new String[request.length][];
for(int i = 0;i<request.length;i++){
req[i] = request[i].split(" ");
}
for (String[] strings : req) {
int n = strings.length;
writer.println("*" + n);
for (String s : strings) {
writer.println("$" + s.getBytes(StandardCharsets.UTF_8).length);
writer.println(s);
}
writer.flush();
}
}
根据响应的类型来读取响应结果
java
private static Object handleResponse (BufferedReader reader) {
// 读取首字节
int prefix = 0;
try {
prefix = reader.read();
} catch (IOException e) {
e.printStackTrace();
}
try {
// 判断数据类型标示
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;
} else if (len == 0) {
return "";
}
//读数据(这里使用的是字符流,直接读一行)
return reader.readLine();
}
case '*' -> {
return readbulkString(reader);
}
default -> throw new RuntimeException("错误的数据格式!");
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private static Object readbulkString (BufferedReader reader) {
// 获取数据大小
int len = 0;
try {
len = Integer.parseInt(reader.readLine());
} catch (IOException e) {
e.printStackTrace();
}
if(len <= 0){
return null;
}
// 接收多个元素
List<Object> list = new ArrayList<>(len);
// 遍历,依次获取每个元素
for(int i = 0;i < len;i++){
list.add(handleResponse(reader));
}
return list;
}
使用单元测试方法,测试发送请求和接收响应
java
@Test
public void test(){
Main.RedisRequest("192.168.45.138:6379",null,"set name xiaoming","get name");
}
测试结果为: