十七,Redis通信协议

91 阅读2分钟

一,RESP协议

1.1 介绍

Redis是一个CS架构的软件,通信一般分两步(不包括pipeline和Pubsub)

  1. 客户端(client)向服务端(server)发送一条命令

  2. 服务端解析并执行命令,返回响应结果给客户端

因此客户端发送命令的格式、服务端响应结果的格式必须有一个规范,这个规范就是通信协议。


而在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

    image-20240906170019054

  • 数组:首字节是*,后面跟上数组元素个数,再跟上元素,数据类型不限

    image-20240906170250671

二,模拟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();
    }
}