【Redis学习】redis通讯协议

服务是一个较抽象的概念,意在帮助使用者达到某种需求。那么对于网络服务来说,通常我们可以将其定义为一个运行在操作系统上的程序,使用者通过网络与其进行交互并得到想要的信息。

客户端和服务器通过TCP连接来进行数据交互,所以它和其它网络服务一样有一个协议。redis服务器接受命令以及命令的参数。服务器会在接到命令之后对命令进行处理,并将命令的回复传送到客户端。具体在网络层上就是:Redis在TCP端口6379上监听到来的连接,客户端连接到来时,Redis服务器为此创建一个TCP连接。在客户端与服务器端之间传输的每个Redis命令或者数据都以\r\n结尾。

redis的通讯协议包括:消息头标识和消息行,消息行里可能还有一个数据块大小的描述。redis是以行来划分的,每行以\r\n结束,且每一行都有一个消息头

消息体一共分为5种类型:

(1)[+]表示一个正确的状态信息,具体信息是:当前行+后面的字符
(2)[-]表示一个错误信息,具体信息是:当前行-后面的字符
(3)[]表示消息体总共有多少行,不包括当前行,后面是具体的行数
(4)[ ]\r\n ] 表 示 下 一 行 数 据 长 度 , 不 包 括 换 行 符 \r \n 的 长 度 , 后面则是对应长度的数据
(5)[:]表示返回一个数值,:后面是相应的数字字符

redis命令会返回多种不同类型的回复,通过检查服务器发回数据的第一个字节,可以确定这个回复是什么类型:

(1)状态回复的第一个字节是[+]
(2)错误回复的第一个字节是[-]
(3)整数回复的第一个字节是[:]
(4)批量回复的第一个字节是[$]
(5)多条批量回复的第一个字节是[*]

具体指令和返回:

1、SET

client : SET MYTEST HELLO

设置MYTEST的值为HELLO。
在redis的通讯协议上会以空格把命令拆分成三行,得到最终的命令如下:

*3\r\n     --消息体总共3行
$3\r\n     --下一行数据的长度“SET”为3
SET\r\n
$6\r\n     --下一行数据的长度“MYTEST”为6
MYTEST\r\n
$5\r\n     --下一行数据的长度“HELLO”为5
HELLO\r\n

server :
服务端操作成功:+OK\r\n
如果服务端返回错误: -错误信息\r\n

2、GET

client : GET MYTEST

产生的通讯指令:

*2\r\n
$3\r\n
GET\r\n
$6\r\n
MYTEST

server : 如果存在这个key则返回:

$5\r\n
HELLO\r\n

3、HKEYS

 client : HKEYS MYTEST

以上命令是获取对应MYTEST有多少个field成员:

*2\r\n
$5\r\n
HKEYS\r\n
$6\r\n
MYTEST\r\n

server :
如果不存在任何字段信息:*0\r\n
如果存在一个AGE字段信息:

*1\r\n
$2\r\n
AGE\r\n

4、HMGET

client :
HMGET MYTEST AGE

以上命令是获取HENRY的QQ信息。

*3\r\n
$5\r\n
HMGET\r\n
$6\r\n
MYTEST\r\n
$3\r\n
AGE\r\n

server :

如果不存在字段值

*1\r\n
$-1\r\n

存在字段值

*1\r\n
$2\r\n
28\r\n

以上主要列举Redis普遍处理的一些情况,由于指令太多就不一一列举了,如果有需要自己实现Client的朋友可以到Redis官方看相关命令文档。redis官方文档

练习小程序:通过socket和redis server进行通信

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
public class SimpleProtocol {
    public static void main(String[] args) throws Exception{
      Socket socket = new Socket();
        socket.setReuseAddress(true);
        //空闲时发送数据包,确认服务端状态
        socket.setKeepAlive(true);
        //关闭Nagle算法,尽快发送
        socket.setTcpNoDelay(true);
        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

你可能感兴趣的:(Redis)