文章目录
- Redis通信协议
- RESP协议
- 数据类型
- 模拟Redis客户端
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建立连接
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 xiaomingsendRequest(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)
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();}
}
根据响应的类型来读取响应结果
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;
}
使用单元测试方法,测试发送请求和接收响应
@Test
public void test(){Main.RedisRequest("192.168.45.138:6379",null,"set name xiaoming","get name");
}
测试结果为: