redis || 通信协议

RESP协议

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

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

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

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

而在Redis中采用的是RESP(Redis Serialization Protocol)协议:

1. Redis 1.2版本引入了RESP协议

2. Redis 2.0版本中成为与Redis服务端通信的标准,称为RESP2

3. Redis 6.0版本中,从RESP2升级到了RESP3协议,增加了更多数据类型并且支持6.0的新特性--客户端缓存。

但目前,默认使用的依然是RESP2协议,也是我们要学习的协议版本(以下简称RESP)。

RESP协议-数据类型

在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. 数组:首字节是 ‘*’,后面跟上数组元素个数,再跟上元素,元素数据类型不限:

模拟Redis客户端

模拟Redis客户端-建立连接

Redis支持TCP通信,因此我们可以使用Socket来模拟客户端,与Redis服务端建立连接:

public class RedisDemo {    
  static Socket s;  static PrintWriter writer;  static BufferedReader reader;    
  public static void main(String[] args) throws IOException {        
    // 1.定义连接参数        
    String host = "172.16.3.152";        
    int port = 6379;        
    // 2.连接 Redis        
    s = new Socket(host, port);        
    // 2.1.获取输入流        
    reader = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));        
    // 2.2.获取输出流        
    writer =new PrintWriter(s.getOutputStream());        
    // TODO 3.发送请求        
    sendRequest();        
    // TODO 4.接收响应        
    Object obj = handleResponse();
    System.out.println(obj);        
    // 5.关闭连接        
        if (reader != null) reader.close();        
    if (writer != null) writer.close();        
    if (s != null) s.close();    
  }    
  private static Object handleResponse() {}    
  private static void sendRequest() {}
}

模拟Redis客户端-发送请求

这里我们以set命令为例,发送请求就是输出下面内容:

private static void sendRequest() {    
  writer.println("*3");    
  writer.println("$3");    
  writer.println("set");    
  writer.println("$4");    
  writer.println("name");     
  writer.println("$6");    
  writer.println("hello");    
  writer.flush();
}
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();
}

模拟Redis客户端-解析结果

响应的结果可能是之前讲的5种数据类型中的任意一种,需要判断后读取:

private static Object handleResponse() {    
  try {        
  // 当前前缀        
    char prefix = (char) reader.read();        
  switch (prefix) {            
    case '+': // 单行字符串,直接返回                
      return reader.readLine();            
    case '-': // 异常,直接抛出                
      throw new RuntimeException(reader.readLine());            
    case ':': // 数值,转为 int 返回                
      return Integer.valueOf(reader.readLine());            
    case '$': // 多行字符串,先读长度               
      int length = Integer.parseInt(reader.readLine());
       // 如果为空,直接返回                
      if(length == 0 || length == -1) return "";         
        // 不为空,则读取下一行                
        return reader.readLine();            
    case '*': // 数组,遍历读取                
       return readBulkString();            
    default:                
       return null;        
    }    
  } catch (IOException e) {        
       throw new RuntimeException(e);    
  }
}
private static List<Object> readBulkString() 
throws IOException {    
  // 当前数组大小    
  int size = Integer.parseInt(reader.readLine());    
  // 数组为空,直接返回 null    
  if(size == 0 || size == -1){        
    return null;    
  }    
  List<Object> rs = new ArrayList<>(size);    
  for (int i = size; i > 0; i--) {        
    try { // 递归读取            
      rs.add(handleResponse());        
    } catch (Exception e) {            
      rs.add(e);        
    }    
  }    
  return rs;
}

模拟Redis客户端-测试

最终,我们测试发送请求和接收响应:

// 3.发送授权请求 auth 123456
sendRequest("auth", "123456");
// 4.接收响应
Object obj = handleResponse();
System.out.println("auth = " + obj);

// 3.发送set请求
sendRequest("set", "name", "hello");
// 4.接收响应
obj = handleResponse();
System.out.println("set = " + obj);

// 3.发送 mget请求
sendRequest("mget", "name", "msg");
// 4.接收响应
obj = handleResponse();
System.out.println("mget = " + obj);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

韩未零

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值