手写一个简单的Redis连接工具
既然要连接Redis服务端,那么我们就要先知道客户端与redis服务端的通信协议protocol
是怎么约定的.
通过查阅redis官方相关资料后发现
文章所涉及的代码:
https://gitee.com/isidea/example-redis-client.git
-
网络层
- 客户端和服务器通过 TCP 连接来进行数据交互, 服务器默认的端口号为 6379 。
- 客户端和服务器发送的命令或数据一律以
\r\n
(CRLF)结尾。
-
新版统一请求协议
新版统一请求协议在 Redis 1.2 版本中引入, 并最终在 Redis 2.0 版本成为 Redis 服务器通信的标准方式。
你的 Redis 客户端应该按照这个新版协议来进行实现。
在这个协议中, 所有发送至 Redis 服务器的参数都是二进制安全(binary safe)的。
以下是这个协议的一般形式:
*<参数数量> CR LF $<参数 1 的字节数量> CR LF <参数 1 的数据> CR LF ... $<参数 N 的字节数量> CR LF <参数 N 的数据> CR LF
命令本身也作为协议的其中一个参数来发送。
举个例子:
set "mykey" "myvalue"
以下是这个命令协议的打印版本:*3 $3 SET $5 mykey $7 myvalue
简单分析一下:
*3
: 参数数量SET
命令本身是一个参数mykey
是一个参数myvalue
也是一个参数一共3个参数.所以就是3$3
: 代表你这个参数的字节数量 看$3
下面的SET
一共是3个字符所以就是$3
而$5
下面的mykey
数一下一共是5个字符所以也就是$5
.要注意,每行结束后后面都会跟一个\r\n
换行这个命令的实际协议值如下:
"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
代码结构
.
|____test
| |____java
|____main
| |____resources
| |____java
| | |____com
| | | |____example
| | | | |____redis
| | | | | |____client
| | | | | | |____Protocol.java
| | | | | | |____Command.java
| | | | | | |____RClient.java
| | | | | | |____Test.java
| | | | | | |____RSocket.java
时序调用图
理论了解完后,开始上代码
最终结果图
既然是网络连接,必然离不开Socket
/**
* 连接通讯层
*
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-16 10:05
*/
public class RSocket {
private Socket socket;
private InputStream inputStream;
private OutputStream outputStream;
@SneakyThrows
public RSocket(String host, Integer port) {
socket = new Socket(host, port);
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
}
/**
* 向Redis服务发送命令
* str: "*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n"
*/
@SneakyThrows
public void send(String str) {
outputStream.write(str.getBytes(Charset.defaultCharset()));
}
/**
* 读取Redis返回的数据
*/
@SneakyThrows
public String read() {
byte[] bytes = new byte[1024];
int read = inputStream.read(bytes);
return new String(bytes, 0, read, Charset.defaultCharset());
}
}
@SneakyThrows : 只是lombook的一个注解,我是为了代码里不必要写太多的try(){}catch{}不美观加一个注解而已.
构造器内通过TCP连接到redis服务端
这段代码内主要的还是send方法,参数怎么来???
既然要连接redis那么就要按照redis定义的通信协议标准来,那我我们就去定义一下这个标准
/**
* Redis协议 @link http://doc.redisfans.com/topic/protocol.html
*
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-16 10:22
*/
public class Protocol {
static final String LINE = "\r\n";
static final String STRINGLINGTH = "$";
static final String STAR = "*";
public static String reversion(Command command,byte[]... bytes){
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(STAR).append(1 + bytes.length).append(LINE);
stringBuffer.append(STRINGLINGTH).append(command.toString().length()).append(LINE);
stringBuffer.append(command.toString()).append(LINE);
for (byte[] aByte : bytes) {
stringBuffer.append(STRINGLINGTH).append(aByte.length).append(LINE);
stringBuffer.append(new String(aByte, Charset.defaultCharset())).append(LINE);
}
return stringBuffer.toString();
}
}
/**
* 命令类型
*
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-16 10:19
*/
public enum Command {
GET,
SET,
INCR,
;
}
标准定义完毕后编写API层
/**
* API层
*
* @author Itachi is.xianglei@gmail.com
* @Date 2019-12-16 00:34
*/
public class RClient {
private RSocket socket;
public RClient(String host, Integer port) {
this.socket = new RSocket(host, port);
}
public String set(String key,String value){
String reversion = Protocol.reversion(Command.SET, key.getBytes(),value.getBytes());
socket.send(reversion);
return socket.read();
}
public String get(String key){
socket.send(Protocol.reversion(Command.GET,key.getBytes()));
return socket.read();
}
}
测试
为什么会返回 +OK
呢?
这是因为Redis 命令会返回多种不同类型的回复
通过检查服务器发回数据的第一个字节, 可以确定这个回复是什么类型:
- 状态回复(status reply)的第一个字节是
"+"
- 错误回复(error reply)的第一个字节是
"-"
- 整数回复(integer reply)的第一个字节是
":"
- 批量回复(bulk reply)的第一个字节是
"$"
- 多条批量回复(multi bulk reply)的第一个字节是
"*"
客户端库应该返回 "+"
号之后的所有内容。 比如在在上面的这个例子中, 客户端就应该返回字符串 "OK"
。
状态回复通常由那些不需要返回数据的命令返回,这种回复不是二进制安全的,它也不能包含新行。
状态回复的额外开销非常少,只需要三个字节(开头的 "+"
和结尾的 CRLF)。