一、什么是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的实现原理有很大的帮助。

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

  1. Python操作redis系列以 哈希(Hash)命令详解(四)

    # -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1. Hset 命令用于 ...

  2. Redis系列五 Redis持久化

    Redis持久化 一.RDB(Redis DataBase) 1.介绍 在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里. Red ...

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

    转自:http://www.cnblogs.com/zhili/archive/2012/08/18/2634475.html 我们在用Asp.net技术开发Web应用程序后,当用户在浏览器输入一个网 ...

  4. Redis协议详解

    smark Beetle可靠.高性能的.Net Socket Tcp通讯组件 支持flash amf3,protobuf,Silverlight,windows phone Redis协议详解 由于前 ...

  5. Redis系列(二):Redis的数据类型及命令操作

    原文链接(转载请注明出处):Redis系列(二):Redis的数据类型及命令操作 Redis 中常用命令 Redis 官方的文档是英文版的,当然网上也有大量的中文翻译版,例如:Redis 命令参考.这 ...

  6. Redis系列(一):Redis的简介与安装

    原文链接(转载请注明出处):Redis系列(一):Redis的简介与安装 什么是 Redis Redis 是一个使用ANSI C 编写的开源.支持网络协议.基于内存.可选持久性的键值对数据库,它是一个 ...

  7. Redis系列一 Redis安装

    Redis系列一    Redis安装 1.安装所使用的操作系统为Ubuntu16.04 Redis版本为3.2.9 软件一般下载存放目录为/opt,以下命令操作目录均为/opt root@ubunt ...

  8. TCP/IP协议详解概述

    TCP/IP协议详解卷1--第一章概述--读书笔记 作者:vpoet 日期:2015/06/25 注:本系列的文章只是作者对TCP/IP协议的理解,难免会出现纰漏或者不完整,当然也有可能很肤浅,希望大 ...

  9. HTTP 协议详解(转载)

    原文: http://kb.cnblogs.com/page/130970/#httpmeessagestructe HTTP协议详解 当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, ...

随机推荐

  1. 泛微e-cology OA系统远程代码执行漏洞及其复现

    泛微e-cology OA系统远程代码执行漏洞及其复现 2019年9月19日,泛微e-cology OA系统自带BeanShell组件被爆出存在远程代码执行漏洞.攻击者通过调用BeanShell组件中 ...

  2. Go defer使用

    defer使用语法 //defer后面必须是函数调用语句或方法调用语句,不能是其他语句,否则编译器会出错. package main import ( "fmt" ) func f ...

  3. 题解 【洛谷】AT654

    题解 AT654 [役人[错题已隐藏]] 此题题面没搬过来, 会日语的dalao可以自行去ATCoder查看. 给出此题的JavaAC代码: public class Main { public st ...

  4. 《吊打面试官》系列-Redis哨兵、持久化、主从、手撕LRU

    你知道的越多,你不知道的越多 点赞再看,养成习惯 前言 Redis在互联网技术存储方面使用如此广泛,几乎所有的后端技术面试官都要在Redis的使用和原理方面对小伙伴们进行360°的刁难.作为一个在互联 ...

  5. T7

    #include<iostream> #include<cstring> #include<cstdio> #include<algorithm> us ...

  6. STL.h

    最近老是被系统的一些STL卡到飞起,然后就手打了一个STL.h 库函数还没有打完,以后打新的还会再更,大家也可以使用,顺便帮我找一下bug,然后我再改进! template< typename ...

  7. python中实例方法,类方法,静态方法简单理解

    按照字面名称来理解的话: 实例方法就是实例化对象的方法,绑定在实例对象上 类方法就是类自己的方法,不需要实例化对象,类自己就是对象,直接绑定在类上 静态方法就是普通的函数,函数作为对象,不过是封装在类 ...

  8. SD-WAN 本地策略与中心策略配置(三)

    目录 1. Localized Policy配置 2. Centralized Policy配置 3. Application Route and Traffice Policy 1. Localiz ...

  9. CAS3.5.2 Server登录后返回用户信息详细解决方案

    单点登录(Single Sign-On, 简称SSO)是目前比较流行的服务于企业业务整合的解决方案之一,SSO使得在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.大家在使用时CA ...

  10. 解决vue低版本安卓手机兼容性问题

    低版本的安卓手机可能会白屏,是由新特性不支持引起的 解决代码es6新特性兼容问题 1,npm 安装 npm install babel-polyfill npm install es6-promise ...