1139365029
本帖最后由 1139365029 于 2022-9-18 13:56 编辑

本贴已默认你拥有Redis基础,若没有请先前往此处:https://www.mcbbs.net/thread-861073-1-1.html

本贴的主要目的是教大家在不添加任何驱动、前置库、外部依赖等的情况下使用Redis
值得注意的是,本贴的相关操作偏向于底层逻辑,需要一定的编程基础



众所周知,Redis的通讯协议为resp协议
因此,对应的操作步骤如下没错,这些以往交给Redis驱动(例如jedis.jar)去完成的内容,现在需要我们自己亲自去操作了造轮子



回到正题,用户输入的指令与resp协议的数据有什么对应关系呢?
举个例子
  1. set key value
复制代码
需要转换为下列resp协议格式
  1. *3
  2. $3
  3. set
  4. $3
  5. key
  6. $5
  7. value
复制代码
我们一行一行来看,
首先第一行的*3,表示这个指令由3个部分/参数组成,
接下来是每个参数的数据,
第一个$3表示第一个参数的长度(字符串转换为byte[]后的数组成员数),
紧接着就是第一个参数的内容,
第二个$3表示第二个参数的长度,
紧接着就是第二个参数的内容,
同理,接下来的$5就是第三个参数的长度,然后是内容,
注意,包括来自Redis返回的数据,换行符均为\r\n,并且数据由\r\n结尾,
并且传输的数据应使用byte数组,而不是String字符串,
接下来再举个例子
  1. ping
复制代码
转换后的结果为
  1. *1
  2. $4
  3. ping
复制代码
最后再举个例子
  1. keys *
复制代码
转换后的结果为
  1. *2
  2. $4
  3. keys
  4. $1
  5. *
复制代码
好了,接下来是解析来自Redis的resp协议格式的数据,
这里我们需要知道Redis会发送什么样的数据,
首先是错误信息,指令异常、执行错误、查询错误等情况,Redis会返回此信息,
这类信息的格式由减号开头,后面跟报错信息,例如
  1. -BaLaBaLa
复制代码
接着是执行成功的信息,set指令、ping指令等回返回此信息,
这类信息的格式由加号开头,后面跟相关信息,例如
  1. +OK
复制代码
接着是整数信息,查询数量、列表操作等回返回此信息,
这类信息的格式由冒号开头,后面跟整数数字,例如
  1. :114514
复制代码
最后是一般的查询数据时返回的内容,
这类信息的格式与我们发送到Redis的数据的格式是相同的,
例如有一个包含3个数据的列表,内容分别是“abc、defg、hijkl”,全部返回时,Redis会返回
  1. *3
  2. $3
  3. abc
  4. $4
  5. defg
  6. $5
  7. hijkl
复制代码
值得注意的是,哈希、字符串等返回的数据只有1个的情况下,Redis会省略掉数据数量,
例如某个字符串的内容是“12345”,则Redis会直接返回
  1. $5
  2. 12345
复制代码
特殊情况:未查询到数据时,列表等多个数据会返回*0,字符串等一个数据会返回$-1



看到这里,想必大家已经知道如何操作了吧,以下为参考代码:
注意此代码的执行效率并非最优,之所以这样写是为了帮助理解
另外此代码只是大致测试了一下,基本没什么问题,不过还是仅供参考
麻了,泥潭的code模块居然会屏蔽掉“$'”,改用quote模块吧
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class test {
        private static Socket socket=null;
       
        //连接redis
        private static void login() {
                try {
                        socket=new Socket("127.0.0.1", 6379);
                        //设置超时时间
                        socket.setSoTimeout(3000);
                } catch (Exception e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                }
        }
       
        //断开redis
        private static void close() {
                if(socket!=null) {
                        try {
                                socket.close();
                        } catch (IOException e) {
                                // TODO 自动生成的 catch 块
                                e.printStackTrace();
                        }
                }
                socket=null;
        }
       
        //将两个数组拼接成一个数组
        public static byte[] addByte(byte[] b1, byte[] b2){
                byte[] re=new byte[b1.length+b2.length];
                System.arraycopy(b1, 0, re, 0, b1.length);
                System.arraycopy(b2, 0, re, b1.length, b2.length);
                return re;
        }
       
        //redis操作
        private static List<byte[]> runCommand(List<byte[]> command) {
                //拼接指令
                //拼接参数个数
                byte[] info=("*"+command.size()+"\r\n").getBytes();
                for(byte[] b:command) {
                        //拼接参数长度
                        info=addByte(info,("$"+b.length+"\r\n").getBytes());
                        //拼接参数内容
                        info=addByte(info,b);
                        //拼接换行符
                        info=addByte(info,"\r\n".getBytes());
                }
                List<byte[]> data=null;
                try {
                        //发送数据
                        socket.getOutputStream().write(info);
                        //准备读取数据
                        InputStream is=socket.getInputStream();
                        //读取并解析数据
                        data=parseData(is);
                } catch (IOException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                }
                //返回读取到的内容
                return data;
        }
       
        //读取并解析redis返回的数据
        //返回解析后的结果
        private static List<byte[]> parseData(InputStream is) throws IOException {
                List<byte[]> re=new ArrayList<byte[]>();
                byte[] b=new byte[1];
                //读取第一个字符
                is.read(b);
                if(b[0]=='-') {
                        //redis指令执行错误
                        StringBuilder sb=new StringBuilder();
                        sb.append(new String(b));
                        while(true) {
                                is.read(b);
                                if(b[0]=='\r') {
                                        //将最后的\n读取完,以便下次使用InputStream
                                        is.read(b);
                                        break;
                                }
                                sb.append(new String(b));
                        }
                        re.add(sb.toString().getBytes());
                }else if(b[0]=='+') {
                        //redis指令执行成功
                        StringBuilder sb=new StringBuilder();
                        sb.append(new String(b));
                        while(true) {
                                is.read(b);
                                if(b[0]=='\r') {
                                        //将最后的\n读取完,以便下次使用InputStream
                                        is.read(b);
                                        break;
                                }
                                sb.append(new String(b));
                        }
                        re.add(sb.toString().getBytes());
                }else if(b[0]==':'){
                        //返回了一个整数
                        StringBuilder sb=new StringBuilder();
                        //第一个字符是冒号,需不需要一起返回随你
                        //sb.append(new String(b));
                        while(true) {
                                is.read(b);
                                if(b[0]=='\r') {
                                        //将最后的\n读取完,以便下次使用InputStream
                                        is.read(b);
                                        break;
                                }
                                sb.append(new String(b));
                        }
                        re.add(sb.toString().getBytes());
                }else if(b[0]=='$') {
                        //返回了正在查询的数据,并且该数据的数量只有1个
                        StringBuilder sb=new StringBuilder();
                        //读取对应数据的长度
                        while(true) {
                                is.read(b);
                                if(b[0]=='\r') {
                                        break;
                                }
                                sb.append(new String(b));
                        }
                        //长度
                        int len=Integer.parseInt(sb.toString());
                        //下一个字符是\n,跳过
                        is.read(b);
                        if(len==-1) {
                                //没有数据
                                return re;
                        }
                        //按照刚才读取的长度,读取数据内容
                        byte[] info=new byte[len];
                        is.read(info, 0, len);
                        re.add(info);
                        //将最后的\r\n读取完,以便下次使用InputStream
                        is.read(b);
                        is.read(b);
                }else if(b[0]=='*') {
                        //返回了正在查询的数据,并且该数据的数量有复数个
                        StringBuilder sb=new StringBuilder();
                        //读取数据的数量
                        while(true) {
                                is.read(b);
                                if(b[0]=='\r') {
                                        break;
                                }
                                sb.append(new String(b));
                        }
                        //数量
                        int num=Integer.parseInt(sb.toString());
                        //下一个字符是\n,跳过
                        is.read(b);
                        if(num==0) {
                                //没有数据
                                return re;
                        }
                        //按照刚才读取的数量,依次读取数据内容
                        for(int i=0;i<num;i++) {
                                //读取数据的长度
                                //第1个字符是$,跳过
                                is.read(b);
                                StringBuilder sb2=new StringBuilder();
                                while(true) {
                                        is.read(b);
                                        if(b[0]=='\r') {
                                                break;
                                        }
                                        sb2.append(new String(b));
                                }
                                //长度
                                int len=Integer.parseInt(sb2.toString());
                                //下一个字符是\n,跳过
                                is.read(b);
                                //按照刚才读取的长度,读取数据内容
                                byte[] info=new byte[len];
                                is.read(info, 0, len);
                                re.add(info);
                                //下一个字符是\r,跳过
                                is.read(b);
                                //下一个字符是\n,跳过
                                is.read(b);
                        }
                }else {
                        //未知,应该是网络错误或其它什么异常导致的,亦或者是高版本的新特性
                        StringBuilder sb=new StringBuilder();
                        sb.append(new String(b));
                        while(true) {
                                is.read(b);
                                if(b[0]=='\r') {
                                        //将最后的\n读取完,以便下次使用InputStream
                                        is.read(b);
                                        break;
                                }
                                sb.append(new String(b));
                        }
                        re.add(sb.toString().getBytes());
                }
                return re;
        }
       
        //测试连接
        public static void main(String[] args) {
                login();
                showByte(test0());
                showByte(test1());
                showByte(test2());
                showByte(test3());
                showByte(test4());
                close();
        }
       
        //将Redis返回的数据输出到控制台
        private static void showByte(List<byte[]> res) {
                if(res!=null) {
                        for(byte[] b:res) {
                                System.out.println(new String(b));
                        }
                }
        }
       
        //测试ping指令
        private static List<byte[]> test0(){
                //ping
                System.out.println("[debug] ping");
                List<byte[]> res=new ArrayList<byte[]>();
                res.add("ping".getBytes());
                return runCommand(res);
        }
       
        //测试set指令
        private static List<byte[]> test1(){
                //set key1 abcd
                System.out.println("[debug] set key1 abcd");
                List<byte[]> res=new ArrayList<byte[]>();
                res.add("set".getBytes());
                res.add("key1".getBytes());
                res.add("abcd".getBytes());
                return runCommand(res);
        }
       
        //测试get指令
        private static List<byte[]> test2(){
                //get key1
                System.out.println("[debug] get key1");
                List<byte[]> res=new ArrayList<byte[]>();
                res.add("get".getBytes());
                res.add("key1".getBytes());
                return runCommand(res);
        }
       
        //测试lrange指令
        private static List<byte[]> test3(){
                //lrange list1 0 10
                System.out.println("[debug] lrange list1 0 10");
                List<byte[]> res=new ArrayList<byte[]>();
                res.add("lrange".getBytes());
                res.add("list1".getBytes());
                res.add("0".getBytes());
                res.add("10".getBytes());
                return runCommand(res);
        }
       
        //测试hdel指令
        private static List<byte[]> test4(){
                //hdel hashname hashkey
                System.out.println("[debug] hdel hashname hashkey");
                List<byte[]> res=new ArrayList<byte[]>();
                res.add("hdel".getBytes());
                res.add("hashname".getBytes());
                res.add("hashkey".getBytes());
                return runCommand(res);
        }
}



部分Redis指令参考:
登录指令(仅Redis存在连接密码时使用,连接成功后需要先发送此指令才能使用其它指令)
  1. auth 密码
复制代码
切换到指定数据库(编号从0开始)
  1. select 编号
复制代码
获取Redis的信息(包括版本、CPU、内存等)
  1. info all
复制代码



参考文献:resp协议

Joker.s.
可以