首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >利用RESP协议,手搓一个简易Redis客户端

利用RESP协议,手搓一个简易Redis客户端

原创
作者头像
程序员老刘
修改2026-08-10 17:17:34
修改2026-08-10 17:17:34
1150
举报

一、RESP协议简介

RESP(REdis Serialization Protocol)是 Redis 1.2 引入、并在 Redis 2.0 正式使用的通信协议。它是一种直观的文本协议,最大的优点是 简单、易解析、可读性强 。

RESP 协议通过不同的前缀字符来区分数据类型:

前缀

类型

示例

+

简单字符串(Simple String)

+OK\r\n

-

错误(Error)

-ERR unknown command\r\n

整数(Integer)

:1000\r\n

$

批量字符串(Bulk String)

$5\r\nhello\r\n

*

数组(Array)

*2\r\n$3\r\nget\r\n$4\r\nname\r\n

命令格式

客户端发送命令时,使用 数组 格式。以 SET name 小明 为例:

代码语言:txt
复制
*3
$3
SET
$4
name
$6
小明

逐行解读:

- *3 —— 数组有 3 个元素

- $3 —— 第 1 个元素长度 3 字节

- SET —— 第 1 个元素的值

- $4 —— 第 2 个元素长度 4 字节

- name —— 第 2 个元素的值

- $6 —— 第 3 个元素长度 6 字节(中文"小明"在 UTF-8 下占 6 字节)

- 小明 —— 第 3 个元素的值

注意 :每行以 \r\n 结尾,长度是字节数而非字符数,这也是为什么代码中要计算 GetBytes(c).Length 。

二、利用Socket实现思路

实现一个简易 Redis 客户端,核心只需三步:

建立TCP连接 → 按RESP协议拼接命令字节流 → 发送并接收响应

1. 建立 TCP 连接

Redis 默认监听 6379 端口,建立 TCP 连接后即可通信。如果设置了密码,连接后需先发送 AUTH 命令完成认证。

2. 拼接 RESP 命令

将用户输入的命令字符串(如 SET name 小明 )按空格拆分,按 RESP 数组格式拼接成字节流。关键是:

- 按 空格拆分 命令和参数

- 计算每个参数的 UTF-8字节数 (而非字符数)

- 用 *N 声明元素个数, $len 声明每个元素的字节长度

3. 发送 & 接收响应

发送字节流后,通过 Socket 接收服务端返回的数据。这里有一个 常见坑点 :

Redis 返回的数据可能分多个 TCP 包到达,一次 Receive 不一定收完。

因此需要 循环接收 ,通过判断缓冲区是否还有可读数据来决定是否继续读取。

三、代码实现

Java实现

代码语言:java
复制
package org.open.java;

import java.io.*;
import java.net.Socket;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.stream.Stream;

public class MyRedis {
    static Socket socket;
    static PrintWriter writer;
    static BufferedReader reader;
    public static void main(String[] args) {
        try {
            // 创建连接
            socket = new Socket("127.0.0.1", 6379);

            // 获取输出流
            OutputStream outputStream = socket.getOutputStream();
            writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8));

            // 获取输入流
            InputStream inputStream = socket.getInputStream();
            reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));

            sendRedisCode("auth 123123");
            sendRedisCode("set name Tom");
            sendRedisCode("get name");
            sendRedisCode("mget name age addr");
        } catch (IOException e) {
            System.out.println(e);
        }
        finally{
            try {
                // 4.关闭联接
                socket.close();
            } catch (IOException e) {
                System.out.println(e);
            }
        }
    }

    private static void handleRedisResponse() throws IOException {
        String s = reader.readLine();
        System.out.println(s);
        if(s.startsWith("$")){
            long l = Long.parseLong(s.replace("$", ""));
            if(l > 0) {
                System.out.println(reader.readLine());
            }
        }
        else if(s.startsWith("*"))
        {
            long l = Long.parseLong(s.replace("*", ""));
            for (long i = 0; i < l; i++) {
                handleRedisResponse();
            }
        }
    }

    private static void sendRedisCode(String redisCode) throws IOException {
        String[] args = redisCode.split(" ");

        writer.println("*" + args.length);

        for (String arg : args) {
            writer.println("$" + arg.getBytes(StandardCharsets.UTF_8).length);
            writer.println(arg);
        }

        writer.flush();

        System.out.println(redisCode);

        // 3.接收并解析响应
        handleRedisResponse();
        System.out.println("");
    }
}

.NET 实现(C#)

代码语言:dotnet
复制
using System.Net.Sockets;
using System.Text;

// 创建Socket
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);

// 建立连接 
socket.Connect("127.0.0.1", 6379);

sendCode("auth 123123");
sendCode("set name Tom");
sendCode("get name");
sendCode("mget name age addr");

// 关闭Socket
socket.Close();

string sendCode(string code)
{
    // 推送命令
    socket.Send(Encoding.UTF8.GetBytes(getCode(code)));

    // 接收返馈(循环接收,避免数据不全)
    var buffer = new byte[8192];
    var sb = new StringBuilder();
    int received;
    do
    {
        received = socket.Receive(buffer);
        sb.Append(Encoding.UTF8.GetString(buffer, 0, received));
    } while (socket.Available > 0);

    // 解析结果
    var msg = sb.ToString();
    Console.WriteLine(code);
    Console.WriteLine(msg);
    return msg;
}

string getCode(string code)
{
    if (string.IsNullOrWhiteSpace(code)) {
        return "";
    }
    var cs = code.Split(' ');
    StringBuilder sb = new StringBuilder();
    sb.AppendLine("*" + cs.Length);
    foreach (var c in cs)
    { 
        sb.AppendLine($"${Encoding.UTF8.GetBytes(c).Length}");
        sb.AppendLine($"{c}");
    }
    //Console.WriteLine(sb.ToString());
    return sb.ToString();
}

Python实现

代码语言:python
复制
import socket
import select

# 创建Socket并建立连接
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 6379))

def get_code(code: str) -> bytes:
    """将Redis命令转换为RESP协议格式"""
    if not code or not code.strip():
        return b""
    parts = code.split(" ")
    lines = [f"*{len(parts)}"]
    for c in parts:
        byte_len = len(c.encode("utf-8"))
        lines.append(f"${byte_len}")
        lines.append(c)
    resp = "\r\n".join(lines) + "\r\n"
    print(resp, end="")
    return resp.encode("utf-8")

def send_code(code: str) -> str:
    """发送Redis命令并接收响应"""
    # 推送命令
    sock.send(get_code(code))

    # 接收返馈(循环接收,避免数据不全)
    chunks = []
    while True:
        readable, _, _ = select.select([sock], [], [], 0.5)
        if not readable:
            break
        data = sock.recv(8192)
        if not data:
            break
        chunks.append(data)

    # 解析结果
    msg = b"".join(chunks).decode("utf-8")
    print(msg)
    print()
    return msg

# 登录Redis
send_code("auth 123123")

send_code("get name")

# 关闭Socket
sock.close()

五、总结

这个简易客户端只实现了最基本的发送/接收功能,没有做响应解析。如果需要完整解析 RESP 响应,可以根据返回的首字符( + 、 - 、 : 、 $ 、 * )判断类型并逐行解析,那就离一个真正的 Redis 客户端不远了。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 一、RESP协议简介
    • 命令格式
  • 二、利用Socket实现思路
    • 1. 建立 TCP 连接
    • 2. 拼接 RESP 命令
    • 3. 发送 & 接收响应
  • 三、代码实现
    • Java实现
    • .NET 实现(C#)
    • Python实现
  • 五、总结
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档