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 相关文章推荐
Django使用redis配置缓存的方法
Jun 01 Redis
Redis 哨兵集群的实现
Jun 18 Redis
厉害!这是Redis可视化工具最全的横向评测
Jul 15 Redis
关于SpringBoot 使用 Redis 分布式锁解决并发问题
Nov 17 Redis
使用RedisTemplat实现简单的分布式锁
Nov 20 Redis
Redis中缓存穿透/击穿/雪崩问题和解决方法
Dec 04 Redis
Redis集群节点通信过程/原理流程分析
Mar 18 Redis
Redis监控工具RedisInsight安装与使用
Mar 21 Redis
Redis分布式锁的7种实现
Apr 01 Redis
基于Redis6.2.6版本部署Redis Cluster集群的问题
Apr 01 Redis
redis 解决库存并发问题实现数量控制
Apr 08 Redis
Redis实战高并发之扣减库存项目
Apr 14 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
php 空格,换行,跳格使用说明
2009/12/18 PHP
Warning: session_destroy() : Trying to destroy uninitialized sessionq错误
2011/06/16 PHP
PHP提示Warning:phpinfo() has been disabled函数禁用的解决方法
2014/12/17 PHP
php上传文件并显示上传进度的方法
2015/03/24 PHP
详解Window7 下开发php扩展
2015/12/31 PHP
PHPCMS手机站伪静态设置详细教程
2017/02/06 PHP
Laravel网站打开速度优化的方法汇总
2017/07/16 PHP
根据身份证号自动输出相关信息(籍贯,出身日期,性别)
2013/11/15 Javascript
使用jQuery在对象中缓存选择器的简单方法
2015/06/30 Javascript
js+css实现超简洁的二级下拉菜单效果代码
2015/09/07 Javascript
jQuery表格插件datatables用法详解
2020/11/23 Javascript
Bootstrap中定制LESS-颜色及导航条(推荐)
2016/11/21 Javascript
jQuery插件FusionCharts绘制的3D双柱状图效果示例【附demo源码】
2017/04/20 jQuery
基于JS实现限时抢购倒计时间表代码
2017/05/09 Javascript
简单谈谈js的数据类型
2017/09/25 Javascript
使用Bootstrap + Vue.js实现表格的动态展示、新增和删除功能
2017/11/27 Javascript
在nginx上部署vue项目(history模式)的方法
2017/12/28 Javascript
Vue中插入HTML代码的方法
2018/09/21 Javascript
Electron autoUpdater实现Windows安装包自动更新的方法
2018/12/24 Javascript
nodejs基础之buffer缓冲区用法分析
2018/12/26 NodeJs
详解在微信小程序的JS脚本中使用Promise来优化函数处理
2019/03/06 Javascript
es6 filter() 数组过滤方法总结
2019/04/03 Javascript
vue.js实现数据库的JSON数据输出渲染到html页面功能示例
2019/08/03 Javascript
利用JS判断元素是否为数组的方法示例
2021/01/08 Javascript
[03:07]【DOTA2亚洲邀请赛】我们,梦开始的地方
2017/03/07 DOTA
简单介绍Python下自己编写web框架的一些要点
2015/04/29 Python
python读取大文件越来越慢的原因与解决
2019/08/08 Python
Django admin 实现search_fields精确查询实例
2020/03/30 Python
Python如何基于Tesseract实现识别文字功能
2020/06/05 Python
AmazeUI折叠式卡片布局,整合内容列表、表格组件实现
2020/08/20 HTML / CSS
北美领先的智能产品购物网站:Wellbots
2018/06/11 全球购物
教师自荐信
2013/12/10 职场文书
2015年世界无车日活动总结
2015/03/23 职场文书
2015年大学班级工作总结
2015/04/28 职场文书
鲁冰花观后感
2015/06/10 职场文书
MySQL数据库 任意ip连接方法
2022/05/20 MySQL