我们要在 Java 中 *** 作 Redis,怎么做呢?首先我们先来了解一下 Redis Serialization Protocol (Redis 序列化协议),这个是 Redis 提供的一种,客户端和 Redis 服务端通信传输的编码协议,服务端收到消息后,会基于这个约定编码进行解码。
测试使用的redis是安装在虚拟机中的。
-
打开 Wireshark 工具,对 VMnet8 这个网络进行抓包
-
增加过滤条件
ip.dst_host==192.168.221.128 and tcp.port in {6379}
-
使用 RDM 工具连接到 Redis Server 进行 key-value *** 作,比如执行 set name cc
-
通过 Wireshark 工具监控数据包内容,可以看到实际发出的数据包是:
*3rnrnSETrnrnnamernrncc
- 其中 *3 代表参数个数,set name cc, 表示三个参数。
- 表示属性长度,表示包含 3 个字符。
客户端和服务器发送的命令或数据一律以 rn (CRLF 回车 + 换行)结尾。
所以基于这个协议,我们可以自己实现一个 Java 客户端。
自定义客户端下面我们通过抓包相关的命令,了解 Redis 客户端的工作机制。
常量池定义相关常量,包括协议中的相关常用符号及get、set方法。
public class CommandConstant { public static final String START = "*"; public static final String LENGTH = "$"; public static final String LINE = "rn"; public enum CommandEnum { SET, GET } }CustomClientSocket
CustomClientSocket 用来建立网络通信连接,并且发送数据指定到 RedisServer;
public class CustomerRedisClientSocket { private Socket socket; private InputStream inputStream; private OutputStream outputStream; public CustomerRedisClientSocket(String ip, int port) { try { socket = new Socket(ip, port); inputStream = socket.getInputStream(); outputStream = socket.getOutputStream(); } catch (IOException e) { e.printStackTrace(); } } public void send(String cmd) { try { outputStream.write(cmd.getBytes()); } catch (IOException e) { e.printStackTrace(); } } public String read() { byte[] bytes = new byte[1024]; int count = 0; try { count = inputStream.read(bytes); } catch (IOException e) { e.printStackTrace(); } return new String(bytes, 0, count); } }客户端
public class CustomerRedisClient { private CustomerRedisClientSocket customerRedisClientSocket; //建立连接 public CustomerRedisClient(String host, int port) { customerRedisClientSocket = new CustomerRedisClientSocket(host, port); } //设置 public String set(String key, String value) { customerRedisClientSocket.send(convertToCommand(CommandConstant.CommandEnum.SET, key.getBytes(), value.getBytes())); return customerRedisClientSocket.read(); //在等待返回结果的时候,是阻塞的 } //获取 public String get(String key) { customerRedisClientSocket.send(convertToCommand(CommandConstant.CommandEnum.GET, key.getBytes())); return customerRedisClientSocket.read(); } //根据协议转换参数 public static String convertToCommand(CommandConstant.CommandEnum commandEnum, byte[]... bytes) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(CommandConstant.START).append(bytes.length + 1).append(CommandConstant.LINE); stringBuilder.append(CommandConstant.LENGTH).append(commandEnum.toString().length()).append(CommandConstant.LINE); stringBuilder.append(commandEnum.toString()).append(CommandConstant.LINE); for (byte[] by : bytes) { stringBuilder.append(CommandConstant.LENGTH).append(by.length).append(CommandConstant.LINE); stringBuilder.append(new String(by)).append(CommandConstant.LINE); } return stringBuilder.toString(); } }测试
通过自定义客户端向redis存值并读取。
public class MainClient { public static void main(String[] args) { CustomerRedisClient customerRedisClient = new CustomerRedisClient("127.0.0.1", 6379); System.out.println(customerRedisClient.set("customer", "jack")); System.out.println(customerRedisClient.get("customer")); } }
$4 代表长度为4,代表返回结果jack的长度。
总结当我们了解了相关原理后,可以发现我们手写一个自定义客户端其实不是想象中的那么难,虽然我们的这个自定义客户端很简陋…hahhaha…
项目地址项目地址
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)