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 = OutputStream();
InputStream is = InputStream()){
os.write(assemblyCommandForArrays().getBytes());
byte[] bytes=new byte[4096];
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");
String();
}
* 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");
String();
}
/**
* 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");
String();
}
/**
* 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");
String();
}
/**
* 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");
String();
}
}
三、⾃定义简单的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");
}
Bytes(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
* 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.de(String.valueOf(args.length + 1)));
os.write(CR_BYTE);
os.write(LF_BYTE);
os.write(DOLLAR_BYTE);
os.de(String.valueOf(command.name().length())));
os.write(CR_BYTE);
os.write(LF_BYTE);
os.de(command.name()));
os.write(CR_BYTE);
os.write(LF_BYTE);
for (final byte[] arg : args) {
os.write(DOLLAR_BYTE);
os.de(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) {
redis支持的五种数据类型
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 = InputStream();
OutputStream os = OutputStream()){
RedisProtocol.sendCommand(os,RedisProtocol.Command.de(key),de(value));
return getReply(is);
}catch (Exception e) {
Message();
}
}
public String get(String key){
try (Socket socket = new Socket(this.host,this.port);
InputStream is = InputStream();
OutputStream os = OutputStream()){
RedisProtocol.sendCommand(os,RedisProtocol.Command.de(key));
return getReply(is);
}catch (Exception e) {
Message();
}
}
private String getReply(InputStream is){
try {
byte[] bytes = new byte[4096];
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.("toby_2"));
}
}
运⾏结果如下:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论