前言
本篇我们将完成一个RESP的socket的服务端,初步完成一个单机版缓存。
另外在其中我们还需要完成命令的动态路由
源码:https://github.com/weloe/Java-Distributed-Cache
本篇代码:
https://github.com/weloe/Java-Distributed-Cache/tree/master/src/main/java/com/weloe/cache/server
上篇:缓存管理 https://www.cnblogs.com/weloe/p/17068891.html
RESP协议
RESP协议支持5种数据类型:字符串,异常,整数,多行字符串,数组
数据类型由第一个字节进行区分,不同部分使用\r\n
来分开
'+'
: 简单字符串'-'
: 异常':'
: 整数'$'
: 多行字符串'*'
: 数组
简单字符串一般用来返回该操作无误,操作成功,例如返回+ok\r\n
异常: -error msg\r\n
整数:: 1\r\n
多行字符串:
$4\r\n
test
这里的4是实际数据的长度
实际信息为 test
数组:
*2\r\n
$2\r\n
ab\r\n
$3\r\n
cde\r\n
信息为字符串数组[ab][cde]
实现
https://github.com/weloe/Java-Distributed-Cache/blob/master/src/main/java/com/weloe/cache/util/RESPUtil.java
根据协议我们就能编写出对请求信息的解析类了,我们把它抽象为一个工具类
对返回内容解析的代码
/**
* 读取RESP协议的字节,转为String
*
* @return String
*/
public static String parseRESPBytes(InputStream inputStream) {
byte[] bytes;
String result = null;
try {
while (inputStream.available() == 0) {
}
bytes = new byte[inputStream.available()];
inputStream.read(bytes);
result = new String(bytes);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 解析RESP协议的String
*
* @param raw
* @return
*/
public static Object parseRESPString(String raw) {
byte type = raw.getBytes()[0];
String result = raw.substring(1);
switch (type) {
case '+':
// +ok\r\n
// 读单行
return result.replace("\r\n", "");
case '-':
// 异常
// -Error msg\r\n
throw new RuntimeException(result.replace("\r\n", ""));
case ':':
// 数字
return result.replace("\r\n", "");
case '$':
return result.split("\r\n")[1];
case '*':
// 多行字符串
String[] strList = result.substring(result.indexOf("$")).split("\r\n");
System.out.print("多条批量请求:");
List<String> list = new LinkedList<>();
for (int i = 1; i < strList.length; i += 2) {
System.out.print(strList[i] + " ");
list.add(strList[i]);
}
System.out.println();
return list;
default:
throw new RuntimeException("错误的数据格式");
}
}
发送请求的代码
/**
* 发送RESP请求
* @param host
* @param port
* @param args
* @return
* @throws IOException
*/
public static byte[] sendRequest(String host,Integer port, String ... args) throws IOException {
Socket socket = new Socket(host,port);
RESPRequest request = new RESPRequest(socket);
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8));
sendRequest(writer,args);
byte[] param = request.getBytes();
request.close();
writer.close();
socket.close();
return param;
}
/**
* 发送RESP请求
* @param writer
* @param args
* @throws IOException
*/
public static void sendRequest(PrintWriter writer, String ... args) throws IOException {
writer.println("*"+args.length);
for (String arg : args) {
writer.println("$"+arg.getBytes(StandardCharsets.UTF_8).length);
writer.println(arg);
}
writer.flush();
}
命令的解析
通过RESP协议的实现,我们就能做到对我们的缓存发送命令,那么我们又该怎么对命令进行解析,然后做出对应的操作呢?
这里我们使用前缀树结构来进行动态路由
Node
首先要构建树的节点
需要注意的是只有叶子节点才有pattern
isWild来判断是否有通配符则是为了来匹配用户输入的参数,我们设置路由的时候用:
作为通配符
例如设置路由set :group :k :v