Redis协议:RESP

作者: yingzong | 来源:发表于2016-06-15 23:41 被阅读3486次

    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 : OK
    
    response type is : $
    $ value len : 11
    GET command response : simpleValue
    

    代码执行了两个命令:

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

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

    相关文章

      网友评论

        本文标题:Redis协议:RESP

        本文链接:https://www.haomeiwen.com/subject/tkkmdttx.html