2021年5月26日星期三

如何用Java Socket实现一个简单的Redis客户端

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

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

 

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

字节数组长度

$参数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、未提供更多的数据类型,以及读写方法,大家可以在此基础上包装一下。

 









原文转载:http://www.shaoqun.com/a/760843.html

跨境电商:https://www.ikjzd.com/

outfit:https://www.ikjzd.com/w/938

汇通达:https://www.ikjzd.com/w/1758


Redis是最常见的缓存服务中间件,在java开发中,一般使用jedis来实现。如果不想依赖第三方组件,自己实现一个简单的redis客户端工具,该如何实现呢?本文就是介绍这样一种方法。Redis的协议非常简单,而且输入数据和输出数据都遵循统一的协议,具体规则参考这里:字节数组长度$参数1的值的字符串表示$参数2的值的字节数组长度$参数2的值的字符串表示...$参数n的值的字节数组长度$参数n的值的
国际标准书号:https://www.ikjzd.com/w/174
hunter:https://www.ikjzd.com/w/992
铭宣海淘:https://www.ikjzd.com/w/1551
口述:他们都说我是克夫命(6/6):http://lady.shaoqun.com/m/a/55589.html
亚马逊运营视屏教程10G:https://www.ikjzd.com/tl/108156
亚马逊listing的五点描述这样写,提高转化率有帮助:https://www.ikjzd.com/articles/16342

没有评论:

发表评论