如何用Java Socket实现一个简单的Redis客户端工具
Posted 卡卡西村长的小灶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何用Java Socket实现一个简单的Redis客户端工具相关的知识,希望对你有一定的参考价值。
Redis是最常见的缓存服务中间件,在java开发中,一般使用 jredis 来实现。
如果不想依赖第三方组件,自己实现一个简单的redis客户端工具,该如何实现呢?本文就是介绍这样一种方法。
Redis的协议非常简单,而且输入数据和输出数据都遵循统一的协议,具体规则参考这里:
http://redisdoc.com/topic/protocol.html
最重要的规则有如下几点:
状态回复(status reply)的第一个字节是 "+"
错误回复(error reply)的第一个字节是 "-"
整数回复(integer reply)的第一个字节是 ":"
批量回复(bulk reply)的第一个字节是 "$"
多条批量回复(multi bulk reply)的第一个字节是 "*"
针对上述规则,我们用两个类来实现:
1、SimpleRedisClient类,主要用于发送请求,并读取响应结果(字符串);
2、SimpleRedisData类,用于解析响应结果,把redis统一协议的字符串,解析为具体的对象。
package tebon.kjcrm; 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(60 * 1000); socket.setSendBufferSize(102400); socket.setReceiveBufferSize(102400); } 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; } Object data = new SimpleRedisData(response).getObject(); String res = data != null ? data.toString() : null; 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); Object data = new SimpleRedisData(response).getObject(); return data != null ? data.toString() : null; } 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); if (response == null || response.length() <= 0) { return null; } Object data = new SimpleRedisData(response).getObject(); return data != null ? data.toString() : null; } 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); if (response == null || response.length() <= 0) { return null; } Object data = new SimpleRedisData(response).getObject(); return data != null ? data.toString() : null; } public List<String> getKeys(String pattern) throws IOException { if (socket == null || pattern == null || pattern.isEmpty()) { return null; } String response = execute("KEYS", pattern); if (response == null || response.length() <= 0) { return null; } Object data = new SimpleRedisData(response).getObject(); return data != null ? (List<String>)data : null; } 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 tebon.kjcrm; 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 Object getObject() { if (rawData == null || rawData.length() <= 0) { return null; } char c = rawData.charAt(pos); if (c == \'+\') { return getSingleLineString(); } else if (c == \'-\') { return getErrorString(); } else if (c == \':\') { return getNumberString(); } else if (c == \'$\') { return getBlukString(); } else if (c == \'*\') { return getMultiBulkStringList(); } else { return null; } } private String getSingleLineString() { int i = rawData.indexOf("\\r\\n", pos); if (i > 0) { int from = pos + 1; int to = i; String v = rawData.substring(from, to); pos = to + 2; return v; } return null; } private String getErrorString() { int i = rawData.indexOf("\\r\\n", pos); if (i > 0) { int from = pos + 1; int to = i; String v = rawData.substring(from, to); pos = to + 2; return v; } return null; } private String getNumberString() { int i = rawData.indexOf("\\r\\n", pos); if (i > 0) { int from = pos + 1; int to = i; String v = rawData.substring(from, to); pos = to + 2; return v; } return null; } private String getBlukString() { int i = rawData.indexOf("\\r\\n", pos); if (i > 0) { 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; } } return null; } private List<String> getMultiBulkStringList() { int i = rawData.indexOf("\\r\\n", pos); if (i > 0) { 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(getBlukString()); } return values; } return null; } }
package tebon.kjcrm; import org.junit.jupiter.api.Test; import java.util.List; public class RedisTest { @Test public void test() { SimpleRedisClient client = null; try { client = new SimpleRedisClient("172.16.101.43", 6379, "Tebon@2019"); 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客户端工具的主要内容,如果未能解决你的问题,请参考以下文章