江湖上说,天下武功,无坚不摧,唯快不破,这句话简直是为我量身定制。

我是一个Redis服务,最引以为傲的就是我的速度,我的 QPS 能达到10万级别。

在我的手下有数不清的小弟,他们会时不时到我这来存放或者取走一些数据,我管他们叫做客户端,还给他们起了英文名叫 Redis-client。

有时候一个小弟会来的非常频繁,有时候一堆小弟会同时过来,但是,即使再多的小弟我也能管理的井井有条。

有一天,小弟们问我。

想当年,为了不让小弟们拖垮我傲人的速度,在设计和他们的通信协议时,我绞尽脑汁,制定了下面的三条原则:

  • 实现简单

  • 针对计算机来说,解析速度快

  • 针对人类来说,可读性强

为什么这么设计呢?先来看看一条指令发出的过程,首先在客户端需要对指令操作进行封装,使用网络进行传输,最后在服务端进行相应的解析、执行。

这一过程如果设计成一种非常复杂的协议,那么封装、解析、传输的过程都将非常耗时,无疑会降低我的速度。什么,你问我为什么要遵循最后一条规则?算是对于程序员们的馈赠吧,我真是太善良了。

我把创造出来的这种协议称为 RESP (REdis Serialization Protocol)协议,它工作在 TCP 协议的上层,作为我和客户端之间进行通讯的标准形式。

说到这,我已经有点迫不及待想让你们看看我设计出来的杰作了,但我好歹也是个大哥,得摆点架子,不能我主动拿来给你们看。

所以我建议你直接使用客户端发出一条向服务器的命令,然后取出这条命令对应的报文来直观的看一下。话虽如此,不过我已经被封装的很严实了,正常情况下你是看不到我内部进行通讯的具体报文的,所以,你可以伪装成一个Redis的服务端,来截获小弟们发给我的消息。

实现起来也很简单,我和小弟之间是基于 Socket 进行通讯,所以在本地先启动一个ServerSocket,用来监听Redis服务的6379端口:

public static void server() throws IOException {ServerSocket serverSocket = new ServerSocket(6379);Socket socket = serverSocket.accept();byte[] bytes = new byte[1024];InputStream input = socket.getInputStream();while(input.read(bytes)!=0){System.out.println(new String(bytes));}
}

然后启动redis-cli客户端,发送一条命令:

set key1 value1

这时,伪装的服务端就会收到报文了,在控制台打印了:

*3
$3
set
$4
key1
$6
value1

看到这里,隐隐约约看到了刚才输入的几个关键字,但是还有一些其他的字符,要怎么解释呢,是时候让我对协议报文中的格式进行一下揭秘了。

我对小弟们说了,对大哥说话的时候得按规矩来,这样吧,你们在请求的时候要遵循下面的规则:

*<参数数量> CRLF
$<参数1的字节长度> CRLF
<参数1的数据> CRLF
$<参数2的字节长度> CRLF
<参数2的数据> CRLF
...
$<参数N的字节长度> CRLF
<参数N的数据> CRLF

首先解释一下每行末尾的CRLF,转换成程序语言就是\r\n,也就是回车加换行。看到这里,你也就能够明白为什么控制台打印出的指令是竖向排列了吧。

在命令的解析过程中,setkey1value1会被认为是3个参数,因此参数数量为3,对应第一行的*3

第一个参数set,长度为3对应$3;第二个参数key1,长度为4对应$4;第三个参数value1,长度为6对应$6。在每个参数长度的下一行对应真正的参数数据。

看到这,一条指令被转换为协议报文的过程是不是就很好理解了?

当小弟对我发送完请求后,作为大哥,我就要对小弟的请求进行指令回复了,而且我得根据回复内容进行一下分类,要不然小弟该搞不清我的指示了。

简单字符串

简单字符串回复只有一行回复,回复的内容以+作为开头,不允许换行,并以\r\n结束。有很多指令在执行成功后只会回复一个OK,使用的就是这种格式,能够有效的将传输、解析的开销降到最低。

错误回复

在RESP协议中,错误回复可以当做简单字符串回复的变种形式,它们之间的格式也非常类似,区别只有第一个字符是以-作为开头,错误回复的内容通常是错误类型及对错误描述的字符串。

错误回复出现在一些异常的场景,例如当发送了错误的指令、操作数的数量不对时,都会进行错误回复。在客户端收到错误回复后,会将它与简单字符串回复进行区分,视为异常。

整数回复

整数回复的应用也非常广泛,它以:作为开头,以\r\n结束,用于返回一个整数。例如当执行incr后返回自增后的值,执行llen返回数组的长度,或者使用exists命令返回的0或1作为判断一个key是否存在的依据,这些都使用了整数回复。

批量回复

批量回复,就是多行字符串的回复。它以$作为开头,后面是发送的字节长度,然后是\r\n,然后发送实际的数据,最终以\r\n结束。如果要回复的数据不存在,那么回复长度为-1。

多条批量回复

当服务端要返回多个值时,例如返回一些元素的集合时,就会使用多条批量回复。它以*作为开头,后面是返回元素的个数,之后再跟随多个上面讲到过的批量回复。

到这里,基本上我和小弟之间的通讯协议就介绍完了。刚才你尝试了伪装成一个服务端,这会再来试一试直接写一个客户端来直接和我进行交互吧。

private static void client() throws IOException {String CRLF="\r\n";Socket socket=new Socket("localhost", 6379);try (OutputStream out = socket.getOutputStream()) {StringBuffer sb=new StringBuffer();sb.append("*3").append(CRLF).append("$3").append(CRLF).append("set").append(CRLF).append("$4").append(CRLF).append("key1").append(CRLF).append("$6").append(CRLF).append("value1").append(CRLF);out.write(sb.toString().getBytes());out.flush();try (InputStream inputStream = socket.getInputStream()) {byte[] buff = new byte[1024];int len = inputStream.read(buff);if (len > 0) {String ret = new String(buff, 0, len);System.out.println("Recv:" + ret);}}}
}

运行上面的代码,控制台输出:

Recv:+OK

上面模仿了客户端发出set命令的过程,并收到了回复。依此类推,你也可以自己封装其他的命令,来实现一个自己的Redis客户端,作为小弟,来和我进行通信。

不过记住,要叫我大哥。

推荐好文

>>【练手项目】基于SpringBoot的ERP系统,自带进销存+财务+生产功能

>>分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!

>>能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!

Redis:我是如何与客户端进行通信的相关推荐

  1. 基于 HTML5 WebGL 的 3D 服务器与客户端的通信

    这个例子的初衷是模拟服务器与客户端的通信,我把整个需求简化变成了今天的这个例子.3D 机房方面的模拟一般都是需要鹰眼来辅助的,这样找产品以及整个空间的概括会比较明确,在这个例子中我也加了,这篇文章就算 ...

  2. 带你100% 地了解 Redis 6.0 的客户端缓存

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试文章 近日 Redis 6.0.0 GA 版本发布,这是 Redis 历 ...

  3. 十九、Redis 6.0 的客户端缓存

    一.为什么需要客户端缓存? 我们都知道,使用 Redis 进行数据的缓存的主要目的是减少对 MySQL 等数据库的访问,提供更快的访问速度,毕竟 <Redis in Action> 中提到 ...

  4. Java中利用socket实现简单的服务端与客户端的通信(中级)——实现任意双向通信

    本文计划采用socket实现客户端和服务端的任意双向通信,即客户端可以随时给服务端发消息,服务端也可以随时给客户端发消息,最终结果就是一个类似与QQ的聊天软件的功能. 以下代码可以直接拷贝到Eclip ...

  5. Java中利用socket实现简单的服务端与客户端的通信(基础级)

    在上一篇文章中,简单的介绍了java中入门级的socket编程,简单的实现了客户端像服务器端发送数据,服务器端将数据接收并显示在控制台,没有涉及多线程.上一篇文章的链接:Java中利用socket实现 ...

  6. 【Socket网络编程】6.两个既能收也能发的udp客户端进行通信的原理

    两个既能收也能发的udp客户端进行通信的原理 ​发送数据时可能会用到INADDR_BROADCAST代表255.255.255.255的广播地址,作用范围只能在当前局域网. 接收数据可能会用到INAD ...

  7. linux多个客户端如何通信_linux实现多个客户端通信进阶

    2020.07.31 linux分享 ● ● ● #01#前言 前情回顾: 在整理上学期资料同时将部分代码分享,将以代码书写时间为时间线,按优化程度逐步发出,代码均为在linux下c编程. 上回功能: ...

  8. Linux socket编程(二) 服务器与客户端的通信

    http://www.cnblogs.com/-Lei/archive/2012/09/04/2670964.html 上一篇写了对套接字操作的封装,这一节使用已封装好的Socket类实现服务器与客户 ...

  9. Redis 6.0 的客户端缓存是怎么肥事?一文带你了解!

    来源 | 程序员历小冰 责编 | Carol 封图 | CSDN 付费下载于视觉中国 近日 Redis 6.0.0 GA 版本发布,这是 Redis 历史上最大的一次版本更新,包括了客户端缓存 (Cl ...

最新文章

  1. 05CSS的引入方式
  2. 常见linux命令使用方法(二)
  3. python lamda函数_python 用lambda函数替换for循环的方法
  4. P3507-[POI2010]GRA-The Minima Game【dp,博弈论】
  5. eplise怎么连接数据库_Eclipse连接MySQL数据库(傻瓜篇)
  6. websocket python unity_Unity中Websocket的简单使用
  7. BZOJ 3779 LCT 线段树 DFS序 坑
  8. 脱离微信,在硬件设备运行小程序?小程序硬件框架大揭秘!
  9. 再探java基础——对面向对象的理解(1)
  10. c++创建一个linux deamon进程
  11. ROS实验笔记之——基于ArUco Marker来估算camera的位姿
  12. qq邮箱收信服务器imap,普通IMAP、POP邮箱的设置 教你使用iPhone邮件客户端管理QQ邮箱...
  13. 如何在Mac上为 Apple ID 设置双重认证?
  14. Java文本控件型号_小博老师解析Java核心技术 ——JSwing文本型控件
  15. ansible 学习
  16. 医院室内定位导航,便捷、低成本智慧医院室内地图应用解决方案
  17. java泛型--桥方法
  18. video标签 设置背景图片
  19. 数据库中char和nchar的区别
  20. 『腾讯后台开发』实习生技能要求

热门文章

  1. windows下修改黑苹果config_[Windows]【神器推荐】PearBIOS,一键傻瓜式安装黑苹果
  2. Ackerman函数(递归与非递归算法实现)
  3. 微信小程序_今日头条
  4. 一个基于Rxjava2+MVP的自定义文本阅读器
  5. Lidar_imu自动标定源码阅读(六)——run部分
  6. 2017上海大学计算机考研分数线,2017年上海大学考研复试分数线以及复试通知
  7. ShopsN为何要做真正的免费开源电商系统
  8. 低压抽屉柜常见故障处理方法_低压开关柜的常见故障及处理方法
  9. 设置计算机屏幕保护程序可以使系统节省资源,[判断题] 设置计算机的屏幕保护程序可以使系统节省资源。...
  10. java中浏览器下载文件_浏览器下载java项目中的文件