通信协议
RESP协议
Redis是一个CS架构的软件,通信一般分两步(不包括pipeline和PubSub):
1.客户端(client)向服务端(server)发送一条命令
2.服务端解析并执行命令,返回响应结果给客户端
因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议。
而在Redis中采用的是RESP(Redis Serialization Protocol)协议:
Redis 1.2版本引入了RESP协议
Redis 2.0版本中成为与Redis服务端通信的标准,称为RESP2
Redis 6.0版本中,从RESP2升级到了RESP3协议,增加了更多数据类型并且支持6.0的新特性--客户端缓存
但目前,默认使用的依然是RESP2协议,也是我们要学习的协议版本(以下简称RESP)。
基于Socket的自定义Redis客户端
在RESP中,通过首字节的字符来区分不同数据类型,常用的数据类型包括5种:
1.单行字符串:首字节是 ‘+’ ,后面跟上单行字符串,以CRLF( "\r\n" )结尾。例如返回"OK": "+OK\r\n"
2.错误(Errors):首字节是 ‘-’ ,与单行字符串格式一样,只是字符串是异常信息,例如:"-Error message\r\n"
3.数值:首字节是 ‘:’ ,后面跟上数字格式的字符串,以CRLF结尾。例如:":10\r\n"
4.多行字符串:首字节是 ‘$’ ,表示二进制安全的字符串,最大支持512MB:
如果大小为0,则代表空字符串:"$0\r\n\r\n"
如果大小为-1,则代表不存在:"$-1\r\n"
5.数组:首字节是 ‘*****’,后面跟上数组元素个数,再跟上元素,元素数据类型不限:
指定命令的实现
package com.demo;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
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 {//建立连接String host = "localhost";int port = 6379;socket = new Socket(host, port);//获取输入、输出流writer = new PrintWriter(socket.getOutputStream());//输入流reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));//输出流//发请求sendRequest();//解析响应Object res = handleResponse();System.out.println(res);} catch (Exception e) {throw new RuntimeException(e);} finally {//释放连接try {if (reader!=null) reader.close();} catch (IOException e) {throw new RuntimeException(e);}if (writer!=null) writer.close();try {if (socket!=null) socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}//发送请求(set name 张三)private static void sendRequest() {writer.println("*3");writer.println("$3");writer.println("set");writer.println("$4");writer.println("name");writer.println("$6");writer.println("张三");writer.flush();}//解析响应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.valueOf(reader.readLine());case '$':int len = Integer.parseInt(reader.readLine());//读取长度if (len == 0) return "";else if (len == 1) return null;return reader.readLine();case '*':return handleMultiple();default:throw new RuntimeException("不支持的数据类型");}}private static Object handleMultiple() throws IOException {Integer len = Integer.valueOf(reader.readLine());if (len <= 0) return null;List<Object> list = new ArrayList<>();for (int i = 0; i < len; i++) {list.add(handleResponse());}return list;}
}
将命令改为可以自定义输入命令的方式
package com.demo;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
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 {//建立连接String host = "localhost";int port = 6379;socket = new Socket(host, port);//获取输入、输出流writer = new PrintWriter(socket.getOutputStream());//输入流reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));//输出流//发请求sendRequest("set", "name", "张三");//解析响应Object res = handleResponse();System.out.println(res);sendRequest("set", "num", "22");//解析响应System.out.println(handleResponse());//发请求sendRequest("mget", "name","num");//解析响应System.out.println(handleResponse());} catch (Exception e) {throw new RuntimeException(e);} finally {//释放连接try {if (reader != null) reader.close();} catch (IOException e) {throw new RuntimeException(e);}if (writer != null) writer.close();try {if (socket != null) socket.close();} catch (IOException e) {throw new RuntimeException(e);}}}//发送请求(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();//刷新}//解析响应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.valueOf(reader.readLine());case '$':int len = Integer.parseInt(reader.readLine());//读取长度if (len == 0) return "";else if (len == 1) return null;return reader.readLine();case '*':return handleMultiple();default:throw new RuntimeException("不支持的数据类型");}}private static Object handleMultiple() throws IOException {Integer len = Integer.valueOf(reader.readLine());if (len <= 0) return null;List<Object> list = new ArrayList<>();for (int i = 0; i < len; i++) {list.add(handleResponse());}return list;}
}