服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

服务器之家 - 数据库 - Redis - Java Socket实现Redis客户端的详细说明

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

2021-08-06 17:46卡卡西村长的小灶 Redis

socket编程是一门技术,它主要是在网络通信中经常用到.这篇文章主要介绍了如何用Java Socket实现一个简单的Redis客户端,需要的朋友可以参考下

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 类型的数据,以 * 开头,后面跟着一个整数,表示列表中所有元素的数量,然后就是每一个列表元素的值,循环解析即可。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
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;
    }
 
 
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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;
        }
    }
 
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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客户端的资料请关注服务器之家其它相关文章!

原文链接:https://www.cnblogs.com/lavezhang/archive/2021/05/26/14810078.html

延伸 · 阅读

精彩推荐
  • RedisRedis的配置、启动、操作和关闭方法

    Redis的配置、启动、操作和关闭方法

    今天小编就为大家分享一篇Redis的配置、启动、操作和关闭方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 ...

    大道化简5312019-11-14
  • Redisredis 交集、并集、差集的具体使用

    redis 交集、并集、差集的具体使用

    这篇文章主要介绍了redis 交集、并集、差集的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友...

    xiaojin21cen10152021-07-27
  • RedisRedis 事务知识点相关总结

    Redis 事务知识点相关总结

    这篇文章主要介绍了Redis 事务相关总结,帮助大家更好的理解和学习使用Redis,感兴趣的朋友可以了解下...

    AsiaYe8232021-07-28
  • Redisredis实现排行榜功能

    redis实现排行榜功能

    排行榜在很多地方都能使用到,redis的zset可以很方便地用来实现排行榜功能,本文就来简单的介绍一下如何使用,具有一定的参考价值,感兴趣的小伙伴们...

    乘月归5022021-08-05
  • RedisRedis如何实现数据库读写分离详解

    Redis如何实现数据库读写分离详解

    Redis的主从架构,能帮助我们实现读多,写少的情况,下面这篇文章主要给大家介绍了关于Redis如何实现数据库读写分离的相关资料,文中通过示例代码介绍...

    罗兵漂流记6092019-11-11
  • Redisredis中如何使用lua脚本让你的灵活性提高5个逼格详解

    redis中如何使用lua脚本让你的灵活性提高5个逼格详解

    这篇文章主要给大家介绍了关于redis中如何使用lua脚本让你的灵活性提高5个逼格的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具...

    一线码农5812019-11-18
  • Redis详解Redis复制原理

    详解Redis复制原理

    与大多数db一样,Redis也提供了复制机制,以满足故障恢复和负载均衡等需求。复制也是Redis高可用的基础,哨兵和集群都是建立在复制基础上实现高可用的...

    李留广10222021-08-09
  • RedisRedis全量复制与部分复制示例详解

    Redis全量复制与部分复制示例详解

    这篇文章主要给大家介绍了关于Redis全量复制与部分复制的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis爬虫具有一定的参考学习...

    豆子先生5052019-11-27