服务是一个较抽象的概念,意在帮助使用者达到某种需求。那么对于网络服务来说,通常我们可以将其定义为一个运行在操作系统上的程序,使用者通过网络与其进行交互并得到想要的信息。
客户端和服务器通过TCP连接来进行数据交互,所以它和其它网络服务一样有一个协议。redis服务器接受命令以及命令的参数。服务器会在接到命令之后对命令进行处理,并将命令的回复传送到客户端。具体在网络层上就是:Redis在TCP端口6379上监听到来的连接,客户端连接到来时,Redis服务器为此创建一个TCP连接。在客户端与服务器端之间传输的每个Redis命令或者数据都以\r\n结尾。
redis的通讯协议包括:消息头标识和消息行,消息行里可能还有一个数据块大小的描述。redis是以行来划分的,每行以\r\n结束,且每一行都有一个消息头。
消息体一共分为5种类型:
(1)[+]表示一个正确的状态信息,具体信息是:当前行+后面的字符
(2)[-]表示一个错误信息,具体信息是:当前行-后面的字符
(3)[]表示消息体总共有多少行,不包括当前行,后面是具体的行数
(4)[ ]表示下一行数据长度,不包括换行符\r\n的长度, ] 表 示 下 一 行 数 据 长 度 , 不 包 括 换 行 符 \r \n 的 长 度 , 后面则是对应长度的数据
(5)[:]表示返回一个数值,:后面是相应的数字字符
redis命令会返回多种不同类型的回复,通过检查服务器发回数据的第一个字节,可以确定这个回复是什么类型:
(1)状态回复的第一个字节是[+]
(2)错误回复的第一个字节是[-]
(3)整数回复的第一个字节是[:]
(4)批量回复的第一个字节是[$]
(5)多条批量回复的第一个字节是[*]
具体指令和返回:
1、SET
client : SET MYTEST HELLO
设置MYTEST的值为HELLO。
在redis的通讯协议上会以空格把命令拆分成三行,得到最终的命令如下:
*3\r\n --消息体总共3行
$3\r\n --下一行数据的长度“SET”为3
SET\r\n
$6\r\n --下一行数据的长度“MYTEST”为6
MYTEST\r\n
$5\r\n --下一行数据的长度“HELLO”为5
HELLO\r\n
server :
服务端操作成功:+OK\r\n
如果服务端返回错误: -错误信息\r\n
2、GET
client : GET MYTEST
产生的通讯指令:
*2\r\n
$3\r\n
GET\r\n
$6\r\n
MYTEST
server : 如果存在这个key则返回:
$5\r\n
HELLO\r\n
3、HKEYS
client : HKEYS MYTEST
以上命令是获取对应MYTEST有多少个field成员:
*2\r\n
$5\r\n
HKEYS\r\n
$6\r\n
MYTEST\r\n
server :
如果不存在任何字段信息:*0\r\n
如果存在一个AGE字段信息:
*1\r\n
$2\r\n
AGE\r\n
4、HMGET
client :
HMGET MYTEST AGE
以上命令是获取HENRY的QQ信息。
*3\r\n
$5\r\n
HMGET\r\n
$6\r\n
MYTEST\r\n
$3\r\n
AGE\r\n
server :
如果不存在字段值
*1\r\n
$-1\r\n
存在字段值
*1\r\n
$2\r\n
28\r\n
以上主要列举Redis普遍处理的一些情况,由于指令太多就不一一列举了,如果有需要自己实现Client的朋友可以到Redis官方看相关命令文档。redis官方文档
练习小程序:通过socket和redis server进行通信
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
public class SimpleProtocol {
public static void main(String[] args) throws Exception{
Socket socket = new Socket();
socket.setReuseAddress(true);
//空闲时发送数据包,确认服务端状态
socket.setKeepAlive(true);
//关闭Nagle算法,尽快发送
socket.setTcpNoDelay(true);
socket.setSoLinger(true, 0);
//连接server
socket.connect(new InetSocketAddress("localhost", 6379), 3000);
//设置读取时超时时间
socket.setSoTimeout(3000);
OutputStream os = socket.getOutputStream();
/**
* SET 命令
* 协议: array 3个元素 SET simpleKey simpleValue
*/
os.write(getBytes("*3\\r\\n$3\\r\\nSET\\r\\n$9\\r\\nsimpleKey\\r\\n$11\\r\\nsimpleValue\\r\\n"));
os.flush();
InputStream is = socket.getInputStream();
/**
* 解析SET命令的返回结果
*/
String result = analysisResult(is);
System.out.println("SET command response : " + result);
System.out.println();
/**
* GET 命令
* 协议: array 2个元素 GET simpleKey
*/
os.write(getBytes("*2\\r\\n$3\\r\\nGET\\r\\n$9\\r\\nsimpleKey\\r\\n"));
os.flush();
/**
* 解析GET命令返回结果
*/
String value = analysisResult(is);
System.out.println("GET command response : " + value);
is.close();
os.close();
socket.close();
}
/**
* 解析返回结果
* @param is
* @return
* @throws Exception
*/
private static String analysisResult(InputStream is) throws Exception{
/**
* 第一个字节指定返回的数据结构类型
*/
byte type = (byte)is.read();
System.out.println("response type is : " + (char)type);
if(type == '+'){
//Simple String类型
return readCRLF(is);
}else if(type == '$'){
//Bulk String类型
int len = readIntCRLF(is);
System.out.println("$ value len : " + len);
return readFixedLen(is, len);
}
return null;
}
/**
* 读取int值,直到遇到CRLF
* @param is
* @return
* @throws Exception
*/
private static int readIntCRLF(InputStream is) throws Exception{
return Integer.parseInt(readCRLF(is));
}
/**
* 读取字符串,直到遇到CRLF
* @param is
* @return
* @throws Exception
*/
private static String readCRLF(InputStream is) throws Exception{
byte b = (byte)is.read();
StringBuilder sb = new StringBuilder();
//不是最后一个输入字节时
while(b != -1){
//判断是否是CR,如果不是加入sb中
if(b != '\\r'){
sb.append((char)b);
}else{
//如果是CR,继续读取一个字节,如果不是LF,报错
byte oneMore = (byte)is.read();
if(oneMore != '\\n'){
throw new RuntimeException("CRLF error!");
}else{
break;
}
}
b = (byte)is.read();
}
return sb.toString();
}
/**
* 读取固定字节长度的字符串
* @param is
* @param len
* @return
* @throws Exception
*/
private static String readFixedLen(InputStream is, int len) throws Exception{
byte[] bytes = new byte[len];
for(int i = 0; i < len; i++){
bytes[i] = (byte)is.read();
}
//CR
is.read();
//LF
is.read();
return new String(bytes, "UTF-8");
}
private static byte[] getBytes(String str) throws Exception{
return str.getBytes(Charset.forName("UTF-8"));
}
}
运行结果;
response type is : +
SET command response : OK
response type is : $
$ value len : 11
GET command response : simpleValue