【Redis】Wireshark 分析 Redis 通讯

Posted by 西维蜀黍 on 2020-05-24, Last Modified on 2021-09-21

Background

Redis client与Redis server在默认情况下的通讯,是不加密的。而且他们之间的通讯基于TCP。

因此,我们可以通过Wireshark来获取它们之间的通讯内容。

Redis server: 192.168.184:6379

Redis client: 192.168.31.169

Experiment

当client在设置一个key后:

192.168.31.184:6379> set sw_test_key4 sw_test_value4
OK

client (192.168.31.169) 向 server(192.168.184)发送一个TCP包(序号为762):

随后,server返回 +OK,并附上ACK flag(表示上一个client发过来的TCP包收到了),对应序号为831的包:

client再发送一个包含ACK flag的TCP 包,以表示自己收到了刚才的831包。

至此,整个通讯过程结束。

RESP(REdis Serialization Protocol)

Redis使用 RESP(REdis Serialization Protocol)协议进行通讯。

这意味着,client需要将一个操作序列化成byte[](在上面的例子中,TCP包(序号为762)中的body内容,就是将上面的set操作序列化后的byte[])。

Data Types

In RESP, the type of some data depends on the first byte:

  • For Simple Strings the first byte of the reply is “+”
  • For Errors the first byte of the reply is “-”
  • For Integers the first byte of the reply is “:”
  • For Bulk Strings the first byte of the reply is “$”
  • For Arrays the first byte of the reply is “*

RESP Arrays

RESP Arrays are sent using the following format:

  • A * character as the first byte, followed by the number of elements in the array as a decimal number, followed by CRLF.
  • An additional RESP type for every element of the Array.

So an empty Array is just the following:

"*0\r\n"

While an array of two RESP Bulk Strings “foo” and “bar” is encoded as:

"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"

As you can see after the *CRLF part prefixing the array, the other data types composing the array are just concatenated one after the other. For example an Array of three integers is encoded as follows:

"*3\r\n:1\r\n:2\r\n:3\r\n"

RESP Bulk Strings

Bulk Strings are used in order to represent a single binary safe string up to 512 MB in length.

Bulk Strings are encoded in the following way:

  • A “$” byte followed by the number of bytes composing the string (a prefixed length), terminated by CRLF.
  • The actual string data.
  • A final CRLF.

So the string “foobar” is encoded as follows:

"$6\r\nfoobar\r\n"

When an empty string is just:

"$0\r\n\r\n"

RESP Bulk Strings can also be used in order to signal non-existence of a value using a special format that is used to represent a Null value. In this special format the length is -1, and there is no data, so a Null is represented as:

"$-1\r\n"

This is called a Null Bulk String.

The client library API should not return an empty string, but a nil object, when the server replies with a Null Bulk String. For example a Ruby library should return ’nil’ while a C library should return NULL (or set a special flag in the reply object), and so forth.

Sending commands to a Redis Server

A client sends the Redis server a RESP Array consisting of just Bulk Strings.

我们把上面的TCP包(序号为762)中的body内容使用ASCII解码,这可以让我们更直观的了解RESP:

因为client发给server的总是一个RESP Array,因此上面的第一个字符是 *,3表示这个RESP Array有3个元素。

# 这是一个RESP Array,共有3个元素
*3 

# 下面是一个 RESP Bulk Strings,字节长度为3
$3 
set 

# 下面是一个 RESP Bulk Strings,字节长度为12
$12 
sw_test_key4 

# 下面是一个 RESP Bulk Strings,字节长度为12
$14 
sw_test_value4 

Sending a command‘s answer to a Redis Client

192.168.31.184:6379> get sw_test_key4
"sw_test_value4"

server的返回内容:

其实也和上面的例子类似:

# $表示这是一个Bulk Strings,长度为12个byte[]
$14 
sw_test_value4 

如果我们读取一个包含中文的value,中文会被UTF-8编码:

192.168.31.184:6379> set sw_test_key5 测试
OK
192.168.31.184:6379> get sw_test_key5
"\xe6\xb5\x8b\xe8\xaf\x95"

Reference