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 相关文章推荐
详解缓存穿透击穿雪崩解决方案
May 28 Redis
5分钟教你docker安装启动redis全教程(全新方式)
May 29 Redis
详解Redis集群搭建的三种方式
May 31 Redis
在项目中使用redis做缓存的一些思路
Sep 14 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
分布式Redis Cluster集群搭建与Redis基本用法
Feb 24 Redis
源码分析Redis中 set 和 sorted set 的使用方法
Mar 22 Redis
使用Redis做预定库存缓存功能
Apr 02 Redis
Redis 报错 error:NOAUTH Authentication required
May 15 Redis
Redis实现短信验证码登录的示例代码
Jun 14 Redis
Redis主从复制操作和配置详情
Sep 23 Redis
python中使用redis用法详解
Dec 24 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
smarty的保留变量问题
2008/10/23 PHP
关于ob_get_contents(),ob_end_clean(),ob_start(),的具体用法详解
2013/06/24 PHP
百度站点地图(百度sitemap)生成方法分享
2014/01/09 PHP
Zend Framework+smarty用法实例详解
2016/03/19 PHP
PHP实现批量清空删除指定文件夹所有内容的方法
2017/05/30 PHP
JQuery判断子iframe何时加载完成解决方案
2013/08/20 Javascript
各种页面定时跳转(倒计时跳转)代码总结
2013/10/24 Javascript
js中document.write的那点事
2014/12/12 Javascript
详解JS正则replace的使用方法
2016/03/06 Javascript
底部悬浮通栏可以关闭广告位的实现方法
2016/06/01 Javascript
JQuery异步加载PartialView的方法
2016/06/07 Javascript
在node.js中怎么屏蔽掉favicon.ico的请求
2017/03/01 Javascript
微信小程序中时间戳和日期的相互转换问题
2018/07/09 Javascript
详解React服务端渲染从入门到精通
2019/03/28 Javascript
Vue Extends 扩展选项用法完整实例
2019/09/17 Javascript
node.js中path路径模块的使用方法实例分析
2020/02/13 Javascript
[15:20]DOTA2亚洲邀请赛总决赛开幕式表演:羽泉献唱
2017/04/05 DOTA
python中的__slots__使用示例
2015/02/26 Python
Python的Flask框架中web表单的教程
2015/04/20 Python
python基础_文件操作实现全文或单行替换的方法
2017/09/04 Python
python里使用正则表达式的组嵌套实例详解
2017/10/24 Python
python 3.5实现检测路由器流量并写入txt的方法实例
2017/12/17 Python
Python实现从log日志中提取ip的方法【正则提取】
2018/03/31 Python
python3+pyqt5+itchat微信定时发送消息的方法
2019/02/20 Python
python从list列表中选出一个数和其对应的坐标方法
2019/07/20 Python
Python 实现Image和Ndarray互相转换
2020/02/19 Python
解决Ubuntu18中的pycharm不能调用tensorflow-gpu的问题
2020/09/17 Python
简单掌握CSS3将文字描边及填充文字颜色的方法
2016/03/07 HTML / CSS
HTML5移动端开发遇见的东西
2019/10/11 HTML / CSS
冰淇淋店创业计划书范文
2013/12/27 职场文书
会计专业毕业生求职信分享
2014/01/03 职场文书
英语教师岗位职责
2014/03/16 职场文书
安全教育月活动总结
2014/05/05 职场文书
绿色环保演讲稿
2014/05/10 职场文书
正确使用MySQL INSERT INTO语句
2021/05/26 MySQL
Java获取e.printStackTrace()打印的信息方式
2021/08/07 Java/Android