Redis系列(五):Redis的RESP协议详解

一、什么是RESP

  Redis是Redis序列化协议,Redis客户端RESP协议与Redis服务器通信。Redis协议在以下几点之间做出了折衷:

  • 简单的实现
  • 快速地被计算机解析
  • 简单得可以能被人工解析

二、RESP协议描述

  RESP协议在Redis 1.2中引入,但在Redis 2.0中成为与Redis服务器通信的标准方式。这个通信方式就是Redis客户端实现的协议。RESP实际上是一个序列化协议,它支持以下数据类型:简单字符串、错误、整数、大容量字符串和数组。

  1、RESP在Redis中用作请求-响应协议的方式如下:

  • 客户端将命令以批量字符串的RESP数组的形式发送到Redis服务器,如下:
SET mykey myvalue
*3
$3
SET
$5
mykey
$7
myvalue

*3:SET mykey myvalue 这数组的长度
$3:表示下面的字符长度是3,这里是SET长度是
$5:表示下面的字符的长度是5,这里是mykey的长度
$7:表示下面的字符的长度是7,这里是myvalue的长度
  • 服务器根据命令实现使用其中一种RESP类型进行响应

  2、在RESP中,某些数据的类型取决于第一个字节:

  • For Simple Strings the first byte of the reply is "+"   简单字符串回复的第一个字节将是“+”

  比如:向服务器发送"set toby xu"命令,实际上服务器的返回是:"+OK\r\n"

  • For Errors the first byte of the reply is "-"   错误消息,回复的第一个字节将是“-”

  比如:向服务器发送"add toby xu"命令,实际上服务器的返回是:"-ERR unknown command `add`, with args beginning with: `toby`, `xu`,\r\n"

  • For Integers the first byte of the reply is ":"  整型数字,回复的第一个字节将是“:”

  比如:向服务器发送"incr count"命令,实际上服务器的返回是:":6\r\n"

  • For Bulk Strings the first byte of the reply is "$"  批量回复,回复的第一个字节将是“$”

  比如:向服务器发送"get toby"命令,实际上服务器的返回是:"$2\r\nxu\r\n"

  • For Arrays the first byte of the reply is "*"  数组回复的第一个字节将是“*”

  比如:向服务器发送"hgetall toby_h"命令,实际上服务器的返回是:"*4\r\n$4\r\njava\r\n$3\r\n100\r\n$3\r\nc++\r\n$2\r\n80\r\n"

  示例RedisServerReplyTest代码如下:

/**
 * @desc: 测试服务器返回
 * @author: toby
 * @date: 2019/12/5 23:07
 */
public class RedisServerReplyTest {
    public static void main(String[] args) {
        try(Socket socket = new Socket("192.168.160.146",6379);
            OutputStream os = socket.getOutputStream();
            InputStream is = socket.getInputStream()){
            os.write(assemblyCommandForArrays().getBytes());
            byte[] bytes=new byte[4096];
            is.read(bytes);
            System.out.println("服务器真实返回:" + new String(bytes));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * For Simple Strings the first byte of the reply is "+"
     * @return
     */
    private static String assemblyCommandForSimpleStrings() {
        StringBuilder sb=new StringBuilder();
        sb.append("*3").append("\r\n");
        sb.append("$").append("set".length()).append("\r\n");
        sb.append("set").append("\r\n");
        sb.append("$").append("toby".length()).append("\r\n");
        sb.append("toby").append("\r\n");
        sb.append("$").append("xu".length()).append("\r\n");
        sb.append("xu").append("\r\n");
        return sb.toString();
    }

    /**
     * For Errors the first byte of the reply is "-"
     * @return
     */
    private static String assemblyCommandForErrors() {
        StringBuilder sb=new StringBuilder();
        sb.append("*3").append("\r\n");
        sb.append("$").append("set".length()).append("\r\n");
        sb.append("add").append("\r\n");
        sb.append("$").append("toby".length()).append("\r\n");
        sb.append("toby").append("\r\n");
        sb.append("$").append("xu".length()).append("\r\n");
        sb.append("xu").append("\r\n");
        return sb.toString();
    }

    /**
     * For Integers the first byte of the reply is ":"
     * @return
     */
    private static String assemblyCommandForIntegers() {
        StringBuilder sb=new StringBuilder();
        sb.append("*2").append("\r\n");
        sb.append("$").append("incr".length()).append("\r\n");
        sb.append("incr").append("\r\n");
        sb.append("$").append("count".length()).append("\r\n");
        sb.append("count").append("\r\n");
        return sb.toString();
    }

    /**
     * For Bulk Strings the first byte of the reply is "$"
     * @return
     */
    private static String assemblyCommandForBulkStrings() {
        StringBuilder sb=new StringBuilder();
        sb.append("*2").append("\r\n");
        sb.append("$").append("get".length()).append("\r\n");
        sb.append("get").append("\r\n");
        sb.append("$").append("toby".length()).append("\r\n");
        sb.append("toby").append("\r\n");
        return sb.toString();
    }

    /**
     * For Arrays the first byte of the reply is "*"
     * @return
     */
    private static String assemblyCommandForArrays() {
        StringBuilder sb=new StringBuilder();
        sb.append("*2").append("\r\n");
        sb.append("$").append("hgetall".length()).append("\r\n");
        sb.append("hgetall").append("\r\n");
        sb.append("$").append("toby_h".length()).append("\r\n");
        sb.append("toby_h").append("\r\n");
        return sb.toString();
    }
}

三、自定义简单的Redis Client

  我们现在了解了Redis的RESP协议,并且知道网络层上Redis在TCP端口6379上监听到来的连接,客户端连接到来时,Redis服务器为此创建一个TCP连接。在客户端与服务器端之间传输的每个Redis命令或者数据都以\r\n结尾,那么接下来我们自定义一个简单的Client。

  (1)编解码器Coder:

/**
 * @desc: 编解码器
 * @author: toby
 * @date: 2019/12/6 19:33
 */
public class Coder {
    public static byte[] encode(final String str) {
        try {
            if (str == null) {
                throw new IllegalArgumentException("value sent to redis cannot be null");
            }
            return str.getBytes(RedisProtocol.CHARSET);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }

    public static String decode(final byte[] data) {
        try {
            return new String(data, RedisProtocol.CHARSET);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
}

  (2)Redis协议RedisProtocol:

/**
 * @desc: Redis协议
 * @author: toby
 * @date: 2019/12/6 19:33
 */
public class RedisProtocol {
    public static final String CHARSET = "UTF-8";
    public static final byte DOLLAR_BYTE = ‘$‘;
    public static final byte ASTERISK_BYTE = ‘*‘;
    public static final byte PLUS_BYTE = ‘+‘;
    public static final byte MINUS_BYTE = ‘-‘;
    public static final byte COLON_BYTE = ‘:‘;
    public static final byte CR_BYTE = ‘\r‘;
    public static final byte LF_BYTE = ‘\n‘;

    /**
     * *3
     * $3
     * SET
     * $4
     * toby
     * $2
     * xu
     * @param os
     * @param command
     * @param args
     */
    public static void sendCommand(final OutputStream os, final Command command, final byte[]... args) {
        try {
            os.write(ASTERISK_BYTE);
            os.write(Coder.encode(String.valueOf(args.length + 1)));
            os.write(CR_BYTE);
            os.write(LF_BYTE);
            os.write(DOLLAR_BYTE);
            os.write(Coder.encode(String.valueOf(command.name().length())));
            os.write(CR_BYTE);
            os.write(LF_BYTE);
            os.write(Coder.encode(command.name()));
            os.write(CR_BYTE);
            os.write(LF_BYTE);
            for (final byte[] arg : args) {
                os.write(DOLLAR_BYTE);
                os.write(Coder.encode(String.valueOf(arg.length)));
                os.write(CR_BYTE);
                os.write(LF_BYTE);
                os.write(arg);
                os.write(CR_BYTE);
                os.write(LF_BYTE);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public enum Command{
        SET, GET
    }

}

  (3)自定义Client RedisClient:

/**
 * @desc: 自定义Client
 * @author: toby
 * @date: 2019/12/6 19:31
 */
public class RedisClient {
    private String host;
    private int port;

    public RedisClient(String host,int port){
        this.host = host;
        this.port = port;
    }

    public String set(String key,String value){
        try (Socket socket = new Socket(this.host,this.port);
             InputStream is = socket.getInputStream();
             OutputStream os = socket.getOutputStream()){
            RedisProtocol.sendCommand(os,RedisProtocol.Command.SET,Coder.encode(key),Coder.encode(value));
            return getReply(is);
        }catch (Exception e) {
            return e.getMessage();
        }
    }

    public String get(String key){
        try (Socket socket = new Socket(this.host,this.port);
             InputStream is = socket.getInputStream();
             OutputStream os = socket.getOutputStream()){
            RedisProtocol.sendCommand(os,RedisProtocol.Command.GET,Coder.encode(key));
            return getReply(is);
        }catch (Exception e) {
            return e.getMessage();
        }
    }

    private String getReply(InputStream is){
        try {
            byte[] bytes = new byte[4096];
            is.read(bytes);
            return Coder.decode(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}

  (4)Redis Client 测试 RedisClientTest:

/**
 * @desc: Redis Client 测试
 * @author: toby
 * @date: 2019/12/6 19:35
 */
public class RedisClientTest {
    public static void main(String[] args) {
        RedisClient client = new RedisClient("192.168.160.146",6379);
        System.out.println(client.set("toby_2","xu_2"));
        System.out.println(client.get("toby_2"));
    }
}

  运行结果如下:

  至此自定义的简单的Redis Client完成!!!!!!

四、总结

  通过本章的学习,了解了什么是Redis的RESP协议?Redis协议几个特点:简单的实现;快速地被计算机解析;简单得可以能被人工解析。有了协议,我们就可以通过自定义的Client想Redis服务端发起请求,从而进行操作Redis。对后面理解Redis客户端Jedis的实现原理有很大的帮助。

原文地址:https://www.cnblogs.com/toby-xu/p/11992730.html

时间: 2024-10-12 21:44:37

Redis系列(五):Redis的RESP协议详解的相关文章

redis系列(一)-----日常使用详解

目录 ------------------------------------- Nosql数据库概述 NoSQ数据库的分类 几种常见Nosql功能及应用场景介绍 redis简介 redis应用场景 redis安装与启动 redis服务初始化 redis服务启动与关闭 redis命令行操作 redis的安全设置 redis命令禁用和修改 php程序操作redis服务 php配置session保存到redis python程序操作redis服务 --------------------------

Redis系列--7、RedisTemplate和 Serializer详解

<bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate">    <property name="connectionFactory" ref="connectionFactory" />       <!--如果不配置Serializer,那么存储的时候智能使用String,如

[C#]网络编程系列专题二:HTTP协议详解

一.HTTP协议的简介 HTTP中文为超文本传输协议,从名字上很容易理解,Http协议就是将超文本标记语言的文档(即Html文档)从web服务传送到客户端的浏览器.它属于一个应用层的协议. 二.网络的工作过程 当用户要访问网络中的某个网页时,大致要经过以下几个步骤: 用户首先要确定网页文件所在的URL(统一资源定位符,也就是网页在网络上的家庭住址,通过这个地址就可以找到这个网页)如www.cnblogs.com 浏览器向DNS(域名服务器)发出请求,告诉DNS说:"我要把www.cnblogs.

Handler详解系列(五)——Handler的post()方法详解

MainActivity如下: package cc.testui1; import android.os.Bundle; import android.os.Handler; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.app.Activity; /*

redis系列之redis是什么

一.简介 REmote DIctionary Server(Redis),redis是一个基于内存的单机key/value系统,类似memcached,但支持value为多种形式,包括:字符串(string).链表(list).集合(set).有序集合(sorted set)和hash table 二.特点 1 优点 与memcache和MySQL等类似产品比较,Redis有以下几个优点: (1) 非常丰富的数据结构,且这些数据结构的常见操作均是原子性的: (2) 高速读写.Memcached提

Redis系列一 Redis安装

Redis系列一    Redis安装 1.安装所使用的操作系统为Ubuntu16.04 Redis版本为3.2.9 软件一般下载存放目录为/opt,以下命令操作目录均为/opt [email protected]:/opt# wget http://download.redis.io/releases/redis-3.2.9.tar.gz [email protected]:/opt# tar -zxvf redis-3.2.9.tar.gz [email protected]:/opt/re

10.【Redis系列】Redis的高级应用-GeoHash

原文:10.[Redis系列]Redis的高级应用-GeoHash Redis在3.2版本增加了GEO模板,意味着通过redis可以做附近的人,附近的门店,附近的商场这样的功能. 用数据库来算附近的人 地图元素的位置数据使用二维的经纬度表示,经度范围 (-180, 180],纬度范围 (-90, 90],纬度正负以赤道为界,北正南负,经度正负以本初子午线 (英国格林尼治天文台) 为界,东正西负.比如掘金办公室在望京 SOHO,它的经纬度坐标是 (116.48105,39.996794),都是正数

1.【Redis系列】redis是可以做什么?

原文:1.[Redis系列]redis是可以做什么? Redis是互联网技术领域使用最为广泛的存储中间件.它以其超高的性能.完美的文档.简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评.国内外很多大型网站都在使用Redis,比如Twitter.Youporn.Github.腾讯.阿里.京东.华为等等,很多小型公司也在应用. Redis可以做什么呢 Redis的应用非常广泛,我们梳理下redis可以用在哪些方面. 1.记录帖子的点赞数.评论数和点击数 2.记录用户帖子ID的列表,便于快速

5.【Redis系列】Redis的高级应用-位图

原文:5.[Redis系列]Redis的高级应用-位图 假设一个应用场景:我们需要记录用户一年的签到记录,签到了是1,没签是0,记录365天,当用户上亿后,存储空间是惊人的. 为了解决这个问题,redis提供了位图的数据结构.这样每天的签到记录只占据一个位,365天就是365个位,46个字节完全可以容纳下. 位图不是特殊的数据结构,它的内容就是普通的字符串,也就是byte数组,我们可以用set/get方法来设置和获取位图的内容,也可以使用位图操作getbit和setbit将byte数组看成位数组