Java Socket实现Redis客户端的详细说明


Posted in Redis onMay 26, 2021

Redis是最常见的缓存服务中间件,在java开发中,一般使用 jedis 来实现。

如果不想依赖第三方组件,自己实现一个简单的redis客户端工具,该如何实现呢?本文就是介绍这样一种方法。

Redis的协议非常简单,而且输入数据和输出数据都遵循统一的协议,具体规则参考这里:

http://redisdoc.com/topic/protocol.html

Redis的命令协议:

$参数数量n

$参数1的值的字节数组长度

$参数1的值的字符串表示

$参数2的值的字节数组长度

$参数2的值的字符串表示

...

$参数n的值的字节数组长度

$参数n的值的字符串表示

Redis的返回协议:

1、状态回复(status reply)的第一个字节是 "+",单行字符串;
2、错误回复(error reply)的第一个字节是 "-";
3、整数回复(integer reply)的第一个字节是 ":";
4、批量回复(bulk reply)的第一个字节是 "$";
5、多条批量回复(multi bulk reply)的第一个字节是 "*";
6、所有的命令都是以 \r\n 结尾。

Java代码说明

针对上述规则,我们用两个类来实现:

1、SimpleRedisClient类,主要用于发送请求,并读取响应结果(字符串);

整体比较简单,稍微复杂点的地方就是读取流数据,遇到两种情况就该结束循环,一是返回长度为-1,二是返回字符串以 \r\n 结尾。

如果处理不当,可能会导致 read 阻塞,Socket卡住。

2、SimpleRedisData类,用于解析响应结果,把redis统一协议的字符串,解析为具体的对象。

这部分代码完全是按照协议规则来实现的,通过一个游标 pos 来向前移动,在移动过程中识别不同格式的数据。

最复杂的是 list 类型的数据,以 * 开头,后面跟着一个整数,表示列表中所有元素的数量,然后就是每一个列表元素的值,循环解析即可。

package demo;

import java.io.Closeable;
import java.io.IOException;
import java.net.Socket;
import java.util.List;

public class SimpleRedisClient implements Closeable {

    private String host;
    private int port;
    private String auth;
    private Socket socket = null;

    public SimpleRedisClient(String host, int port, String auth) {
        this.host = host;
        this.port = port;
        this.auth = auth;

        try {
            socket = new Socket(this.host, this.port);
            socket.setSoTimeout(8 * 1000);//8秒
        } catch (Exception ex) {
            socket = null;
            ex.printStackTrace();
        }
    }

    public boolean connect() throws IOException {
        if (socket == null || auth == null || auth.length() <= 0) {
            return false;
        }
        String response = execute("AUTH", auth);
        if (response == null || response.length() <= 0) {
            return false;
        }
        String res = new SimpleRedisData(response).getString();
        return "OK".compareTo(res) == 0;
    }

    @Override
    public void close()  {
        try {
            if (socket != null) {
                socket.shutdownOutput();
                socket.close();
            }
            //System.out.println("closed");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public String getString(String key) {
        if (socket == null || key == null || key.isEmpty()) {
            return null;
        }
        try {
            String response = execute("GET", key);
            return new SimpleRedisData(response).getString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public String setString(String key, String value) {
        if (socket == null || key == null || key.isEmpty()) {
            return null;
        }
        try {
            String response = execute("SET", key, value);
            return new SimpleRedisData(response).getString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    public String deleteKey(String key) throws IOException {
        if (socket == null || key == null || key.isEmpty()) {
            return null;
        }
        String response = execute("DEL", key);
        return new SimpleRedisData(response).getString();
    }

    public List<String> getKeys(String pattern) throws IOException {
        if (socket == null || pattern == null || pattern.isEmpty()) {
            return null;
        }

        String response = execute("KEYS", pattern);
        return new SimpleRedisData(response).getStringList();
    }

    public String execute(String... args) throws IOException {
        if (socket == null || args == null || args.length <= 0) {
            return null;
        }

        //System.out.println(StringUtil.join(args, " "));

        StringBuilder request = new StringBuilder();
        request.append("*" + args.length).append("\r\n");//参数的数量

        for (int i = 0; i < args.length; i++) {
            request.append("$" + args[i].getBytes("utf8").length).append("\r\n");//参数的长度
            request.append(args[i]).append("\r\n");//参数的内容
        }

        socket.getOutputStream().write(request.toString().getBytes());
        socket.getOutputStream().flush();

        StringBuilder reply = new StringBuilder();
        int bufSize = 1024;
        while (true) {
            byte[] buf = new byte[bufSize];
            int len = socket.getInputStream().read(buf);
            if (len < 0) {
                break;
            }
            String str = new String(buf, 0, len);
            reply.append(str);
            if (str.endsWith("\r\n")) {
                break;
            }
        }

        String response = reply.toString();
        //System.out.println("response: " + response);
        return response;
    }


}
package demo;

import java.util.ArrayList;
import java.util.List;

public class SimpleRedisData {

    public SimpleRedisData(String rawData) {
        this.rawData = rawData;
        //System.out.println(rawData);
    }

    private int pos;
    private String rawData;

    public String getString() {
        if (rawData == null || rawData.length() <= 0) {
            return null;
        }
        int i = rawData.indexOf("\r\n", pos);
        if (i <= 0) {
            return null;
        }
        char c = rawData.charAt(pos);
        if (c == '+') {
            int from = pos + 1;
            int to = i;
            String v = rawData.substring(from, to);
            pos = to + 2;
            return v;
        } else if (c == '-') {
            int from = pos + 1;
            int to = i;
            String v = rawData.substring(from, to);
            pos = to + 2;
            return v;
        } else if (c == ':') {
            int from = pos + 1;
            int to = i;
            String v = rawData.substring(from, to);
            pos = to + 2;
            return v;
        } else if (c == '$') {
            int from = pos + 1;
            int to = i;
            int bulkSize = Integer.parseInt(rawData.substring(from, to));
            pos = to + 2;

            from = pos;
            to = pos + bulkSize;
            try {
                //$符号后面的数值是指内容的字节长度,而不是字符数量,所以要转换为二进制字节数组,再取指定长度的数据
                byte[] buf = rawData.substring(from).getBytes("utf-8");
                String v = new String(buf, 0, bulkSize);
                pos = to + 2;
                return v;
            } catch (Exception ex) {
                ex.printStackTrace();
                return null;
            }
        } else {
            return null;
        }
    }

    public List<String> getStringList() {
        if (rawData == null || rawData.length() <= 0) {
            return null;
        }
        int i = rawData.indexOf("\r\n", pos);
        if (i <= 0) {
            return null;
        }
        char c = rawData.charAt(pos);
        if (c == '*') {
            List<String> values = new ArrayList<>();
            int from = pos + 1;
            int to = i;
            int multSize = Integer.parseInt(rawData.substring(from, to));
            pos = to + 2;
            for (int index = 0; index < multSize; index++) {
                values.add(getString());
            }
            return values;
        } else {
            return null;
        }
    }

}
package demo;

import org.junit.jupiter.api.Test;

import java.util.List;

public class RedisTest {

    @Test
    public void test() {
        SimpleRedisClient client = null;
        try {
            client = new SimpleRedisClient("127.0.0.1", 6379, "123456");
            System.out.println("connected: " + client.connect());

            List<String> keyList = client.getKeys("api_*");

            for (int i = 0; i < keyList.size(); i++) {
                System.out.println((i + 1) + "\t" + keyList.get(i));
            }

           System.out.println("keys: " + keyList != null ? keyList.size() : "null");

           System.out.println(client.getString("api_getCustomerName"));

        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (client != null) {
                client.close();
            }
        }
    }

}

优点:

1、不依赖任何第三方组件,可以顺利编译通过;

2、代码极其简单。

不足之处:

1、未考虑并发访问;

2、未提供更多的数据类型,以及读写方法,大家可以在此基础上包装一下。

以上就是如何用Java Socket实现一个简单的Redis客户端的详细内容,更多关于Java Socket Redis客户端的资料请关注三水点靠木其它相关文章!

Redis 相关文章推荐
Redis安装启动及常见数据类型
Apr 14 Redis
redis实现共同好友的思路详解
May 26 Redis
解析高可用Redis服务架构分析与搭建方案
Jun 20 Redis
redis cluster支持pipeline的实现思路
Jun 23 Redis
聊一聊Redis与MySQL双写一致性如何保证
Jun 26 Redis
redis 存储对象的方法对比分析
Aug 02 Redis
基于Redis结合SpringBoot的秒杀案例详解
Oct 05 Redis
SpringBoot整合Redis入门之缓存数据的方法
Nov 17 Redis
解决linux下redis数据库overcommit_memory问题
Feb 24 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 Redis
Redis基本数据类型String常用操作命令
Jun 01 Redis
Redis主从复制操作和配置详情
Sep 23 Redis
redis实现共同好友的思路详解
详解Redis瘦身指南
May 26 #Redis
Redis高级数据类型Hyperloglog、Bitmap的使用
May 24 #Redis
redis实现排行榜功能
May 24 #Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
May 21 #Redis
Redis 配置文件重要属性的具体使用
May 20 #Redis
浅谈redis缓存在项目中的使用
May 20 #Redis
You might like
人尽可用的Windows技巧小贴士之下篇
2007/03/22 PHP
深入解析phpCB批量转换的代码示例
2013/06/27 PHP
ThinkPHP3.1新特性之G方法的使用
2014/06/19 PHP
async和DOM Script文件加载比较
2014/07/20 PHP
PHP+MySQL之Insert Into数据插入用法分析
2015/09/27 PHP
如何使用GDB调试PHP程序
2015/12/08 PHP
WordPress主题制作之模板文件的引入方法
2015/12/28 PHP
PHP与jquery实时显示网站在线人数实例详解
2016/12/02 PHP
PHP简单实现遍历目录下特定文件的方法小结
2017/05/22 PHP
js bind 函数 使用闭包保存执行上下文
2011/12/26 Javascript
jquery实现图片等比例缩放以及max-width在ie中不兼容解决
2013/03/21 Javascript
JavaScript的各种常见函数定义方法
2014/09/16 Javascript
JavaScript移除数组内重复元素的方法
2015/03/18 Javascript
jquery滚动条插件slimScroll使用方法
2017/02/09 Javascript
react-router实现按需加载
2017/05/09 Javascript
JS实现获取进今年第几天是周几的方法分析
2018/06/27 Javascript
对layui中表单元素的使用详解
2018/08/15 Javascript
TypeScript基础入门教程之三重斜线指令详解
2018/10/22 Javascript
监控微信小程序中的慢HTTP请求过程详解
2019/07/05 Javascript
JavaScript ECMA-262-3 深入解析(一):执行上下文实例分析
2020/04/25 Javascript
微信小程序实现多选框功能的实例代码
2020/06/24 Javascript
jQuery开发仿QQ版音乐播放器
2020/07/10 jQuery
浅谈Python中的闭包
2015/07/08 Python
python基础知识小结之集合
2015/11/25 Python
浅析Python中yield关键词的作用与用法
2016/11/29 Python
python使用Flask操作mysql实现登录功能
2018/05/14 Python
安装好Pycharm后如何配置Python解释器简易教程
2019/06/28 Python
Python实现最常见加密方式详解
2019/07/13 Python
解决Django加载静态资源失败的问题
2019/07/28 Python
python已协程方式处理任务实现过程
2019/12/27 Python
英国的知名精品百货公司:House of Fraser(福来德)
2016/08/14 全球购物
活动总结格式范文
2014/04/26 职场文书
事业单位聘任报告
2015/03/02 职场文书
2015年扫黄打非工作总结
2015/05/13 职场文书
2016全国“质量月”活动标语口号
2015/12/26 职场文书
python中的3种定义类方法
2021/11/27 Python