欢迎光临
我们一直在努力

Redis协议:RESP

Redis服务器与客户端通过RESP(REdis Serialization Protocol)协议通信。

RESP协议支持的数据类型

Simple String第一个字节以+开头,随后紧跟内容字符串(不能包含CR LF),最后以CRLF结束。很多Redis命令执行成功时会返回”OK”,”OK”就是一个Simple String

"+OK\\r\\n"

Error的结构与Simple String很像,但是第一个字节以-开头:

"-ERR unknown command 'foobar'"

-符号后的第一个单词代表错误类型,ERR代表一般错误,WRONGTYPE代表在某种数据结构上执行了不支持的操作。

Integer第一个字节以:开头,随后紧跟数字,以CRLF结束:

":1000\\r\\n"

很多Redis命令会返回Integer,例如INCR LLEN等。

Bulk String是一种二进制安全的字符串结构,整个结构包含两部分。第一部分以$开头,后面紧跟字符串的字节长度,CRLF结尾。第二部分是真正的字符串内容,CRLF结尾,最大长度限制为512MB。一个Bulk String结构的”Hello World!”是:

"$12\\r\\nHello World!\\r\\n"

空字符串是:

"$0\\r\\n\\r\\n"  

nil是:

"$-1\\r\\n"

Array也可以看成由两部分组成,第一部分以*开头,后面紧跟一个数字代表Array的长度,以CRLF结束。第二部分是每个元素的具体值,可能是Integer,可能是Bulk StringArray结构的[“hello”, “world”]是:

"*2\\r\\n$5\\r\\nhello\\r\\n$5\\r\\nworld\\r\\n"

Array

"*-1\\r\\n"
通过RESP协议与服务端通信

了解了Redis协议的基本数据类型,就可以通过Socket与Redis Server进行通信:

package me.likeyao.yingzong.example;import java.io.InputStream;import java.io.OutputStream;import java.net.InetSocketAddress;import java.net.Socket;import java.nio.charset.Charset;/** * Created by yingzong on 16/6/15. */public class SimpleProtocol {    public static void main(String[] args) throws Exception{        Socket socket = new Socket();        //TIME_WAIT状态下可以复用端口        socket.setReuseAddress(true);        //空闲时发送数据包,确认服务端状态        socket.setKeepAlive(true);        //关闭Nagle算法,尽快发送        socket.setTcpNoDelay(true);        //调用close方法立即关闭socket,丢弃所有未发送的数据包        socket.setSoLinger(true, 0);        //连接server        socket.connect(new InetSocketAddress("localhost", 6379), 3000);        //设置读取时超时时间        socket.setSoTimeout(3000);        OutputStream os = socket.getOutputStream();        /**         * SET 命令         * 协议: array 3个元素 SET simpleKey simpleValue         */        os.write(getBytes("*3\\r\\n$3\\r\\nSET\\r\\n$9\\r\\nsimpleKey\\r\\n$11\\r\\nsimpleValue\\r\\n"));        os.flush();        InputStream is = socket.getInputStream();        /**         * 解析SET命令的返回结果         */        String result = analysisResult(is);        System.out.println("SET command response : " + result);        System.out.println();        /**         * GET 命令         * 协议: array 2个元素 GET simpleKey         */        os.write(getBytes("*2\\r\\n$3\\r\\nGET\\r\\n$9\\r\\nsimpleKey\\r\\n"));        os.flush();        /**         * 解析GET命令返回结果         */        String value = analysisResult(is);        System.out.println("GET command response : " + value);        is.close();        os.close();        socket.close();    }    /**     * 解析返回结果     * @param is     * @return     * @throws Exception     */    private static String analysisResult(InputStream is) throws Exception{        /**         * 第一个字节指定返回的数据结构类型         */        byte type = (byte)is.read();        System.out.println("response type is : " + (char)type);        if(type == '+'){            //Simple String类型            return readCRLF(is);        }else if(type == '$'){            //Bulk String类型            int len = readIntCRLF(is);            System.out.println("$ value len : " + len);            return readFixedLen(is, len);        }        return null;    }    /**     * 读取int值,直到遇到CRLF     * @param is     * @return     * @throws Exception     */    private static int readIntCRLF(InputStream is) throws Exception{        return Integer.parseInt(readCRLF(is));    }    /**     * 读取字符串,直到遇到CRLF     * @param is     * @return     * @throws Exception     */    private static String readCRLF(InputStream is) throws Exception{        byte b = (byte)is.read();        StringBuilder sb = new StringBuilder();        //不是最后一个输入字节时        while(b != -1){            //判断是否是CR,如果不是加入sb中            if(b != '\\r'){                sb.append((char)b);            }else{                //如果是CR,继续读取一个字节,如果不是LF,报错                byte oneMore = (byte)is.read();                if(oneMore != '\\n'){                    throw new RuntimeException("CRLF error!");                }else{                    break;                }            }            b = (byte)is.read();        }        return sb.toString();    }    /**     * 读取固定字节长度的字符串     * @param is     * @param len     * @return     * @throws Exception     */    private static String readFixedLen(InputStream is, int len) throws Exception{        byte[] bytes = new byte[len];        for(int i = 0; i < len; i++){            bytes[i] = (byte)is.read();        }        //CR        is.read();        //LF        is.read();        return new String(bytes, "UTF-8");    }    private static byte[] getBytes(String str) throws Exception{        return str.getBytes(Charset.forName("UTF-8"));    }}=>response type is : +SET command response : OKresponse type is : $$ value len : 11GET command response : simpleValue

代码执行了两个命令:

SET simpleKey "simpleValue"  => "OK"GET simpleKey => "simpleValue"

analysisResult方法目前只支持了+ $两种格式的解析,其他格式解析方式类似,就不一一实现了。

    原文作者:yingzong
    原文地址: https://www.jianshu.com/p/daa3cb672470
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
未经允许不得转载:后端教程 » Redis协议:RESP