用Python语言实现简单的Redis缓冲数据库驱动库

一、主要目的

用Python语言根据Redis的通信协议,实现简单的set和get命令的驱动库。

二、主要代码

1、工具类

util.py文件,实现缓冲读取类,提供读满数组read_full()和读取一行readline()的函数。

python 复制代码
import socket
import threading


# 封装读取缓冲类
class BuffReader:

    # 构造函数
    # sock是TCP的套接字
    def __init__(self, sock: socket.socket):
        # TCP套接字
        self.__sock = sock

        # 缓冲字节序列
        self.__buff = bytearray()

        # 可重入的排他锁
        self.__lock = threading.RLock()

    # 填充数据
    def __fill(self):
        # 读取底层套接字的8000个字节
        data = self.__sock.recv(8000)
        # 如果为空字节,返回0
        if data == b"":
            return 0
        # 加入缓冲中
        self.__buff.extend(data)
        return len(data)

    # 获取缓冲长度
    # 返回值:代表缓冲字节个数的正整数
    def get_buff_len(self):
        return len(self.__buff)

    # 判断缓冲是否为空
    # 返回值:如果为空返回True,否则返回False
    def empty(self):
        return len(self.__buff) == 0

    def __getmin(self, v1, v2):
        if v1 < v2:
            return v1
        return v2

    # 读取字节序列
    # size是最大的字节个数
    # 返回值:读取的字节序列;如果是空字节序列,代表关闭;
    def read(self, size=1):
        # 加锁
        self.__lock.acquire()

        # 缓冲不够就读取
        if self.empty():
            k = self.__fill()

            if k == 0:
                return b""

        # 获得最小的个数
        min_01 = self.__getmin(self.get_buff_len(), size)

        # 切片复制
        data = self.__buff[0:min_01]

        # 切片删除字节序列
        self.__buff[0:min_01] = b""

        # 解锁
        self.__lock.release()
        return data

    # 读取一行
    def readline(self):
        # 加锁
        self.__lock.acquire()
        line = b""

        while True:
            b = self.read(1)

            if b == b"":
                break
            elif b == b'\r':
                self.read(1)
                break
            else:
                line = line + b

        # 释放锁
        self.__lock.release()
        return line

    # 读取指定个数的字节序列
    def read_full(self, size):
        buff = b""

        # 记录个数
        k = 0

        # 循环读取
        while k < size:
            # 希望本次读取的字节数
            target = 0
            # 如果剩余数大于4024
            if size - k > 4024:
                target = 4024
            else:
                target = size - k

            data = self.read(target)

            # 如果结束,跳出循环
            if data == b"":
                raise IOError("sock closed")
            buff = buff + data
            k = k + len(data)
        return buff

    # 关闭
    def close(self):
        # 清空字节
        self.__buff.clear()

        # 关闭
        self.__sock.close()

2、驱动类

python 复制代码
import util
import socket


# 自定义的Redis驱动类
class HRedis:

    def __init__(self, host="localhost", port=6379):
        self.sock = socket.create_connection((host, port))
        self.reader = util.BuffReader(self.sock)
        self.encoding = "UTF-8"

    # 编码参数
    def __encode_arg(self, arg: str = ""):
        # $ + 参数字节序列长度 + \r\n + 命令字符串 + \r\n
        return "$" + str(len(arg.encode(self.encoding))) + "\r\n" + arg + "\r\n"

    # 设置键
    # name是键名;value是值
    def set(self, name: str, value: str):
        cmd = "*3\r\n$3\r\nSET\r\n" + self.__encode_arg(name) + self.__encode_arg(value)

        # 发送命令的字节序列
        self.sock.sendall(cmd.encode(self.encoding))

        # 读取一行
        line = self.reader.readline()
        # 如果是字节序列 +OK,代表执行成功
        if line == b"+OK":
            return True
        else:
            raise Exception(line.decode(self.encoding))

    # 获取键的值
    def get(self, name: str):
        cmd = "*2\r\n$3\r\nGET\r\n" + self.__encode_arg(name)

        self.sock.sendall(cmd.encode(self.encoding))

        # 读取一行
        line = self.reader.readline()
        line = line.decode(self.encoding)

        # 如果是$开头的字符串
        if line.startswith("$"):
            # 截取值的字节个数
            size = int(line[1:])
            # 读取指定个数的字节序列
            data = self.reader.read_full(size)
            # 读取\r\n
            self.reader.readline()
            # 返回解码的字符串结果
            return data.decode(self.encoding)
        else:
            raise Exception(line[1:])


if __name__ == "__main__":
    con = HRedis()
    result = con.set("v123", "小明")
    print("设置结果:", result)

    print("键的值:", con.get("v123"))

代码图如下:

三、执行结果

注意:需要在本地开启Redis服务端,监听6379端口。

四、Redis的协议格式

Redis采用TCP传输层协议,默认服务器监听6379端口。

(一)客户端发给服务端的命令的数据包格式

1、主要格式

* + 参数个数的十进制整数值 + 参数集合

参数集合由每个参数单元接在一起,连续传输。

2、每个参数单位格式如下:

$ + 参数字节序列长度的十进制整数值 + \r\n + 参数值字节串 + \r\n

3、案例:

设置键vname的值为"Peter",格式如下:

*3\r\n3\\r\\nSET\\r\\n5\r\nvname\r\n$5\r\nPeter\r\n

说明:

"*3\r\n"代表是数组类型,后面有3个元素。

"$3\r\n"代表是多行字符串,有3个字节;"SET\r\n"是指定的命令,占3个字节,用"\r\n"结尾。

后面"vname"占5个字节,"Peter"占5个字节。

(二)结果格式

1、普通文本的结果格式

适合set等不需要具体结果的命令,只返回成功或者失败。

格式:

第1个字符是"+"。

+说明文本\r\n

案例:

+OK\r\n

2、整数

用":"开头。

格式:

: 整数字符串 \r\n

案例:

:2000\r\n

3、错误

格式:

用字符"-"开头。

-错误字符串\r\n

案例:

-ERR wrong number of arguments for 'set' command\r\n

4、多行文本

格式:

$字节长度的十进制整数值\r\n

字节序列\r\n

案例:

"hello world"有11个字节,所以前缀是"$11\r\n"。

参数的字节序列再用"\r\n"结尾。

$11\r\nhello world\r\n

五、功能扩展

添加execute()和read_result()两个函数,更通用,适合所有的命令。

python 复制代码
import util
import socket


# 自定义的Redis驱动类
class HRedis:

    def __init__(self, host="localhost", port=6379):
        self.sock = socket.create_connection((host, port))
        self.reader = util.BuffReader(self.sock)
        self.encoding = "UTF-8"

    # 编码参数
    def __encode_arg(self, arg: str = ""):
        # $ + 参数字节序列长度 + \r\n + 命令字符串 + \r\n
        return "$" + str(len(arg.encode(self.encoding))) + "\r\n" + arg + "\r\n"


    # 设置键
    # name是键名;value是值
    def set(self, name: str, value: str):
        cmd = "*3\r\n$3\r\nSET\r\n" + self.__encode_arg(name) + self.__encode_arg(value)

        # 发送命令的字节序列
        self.sock.sendall(cmd.encode(self.encoding))

        # 读取一行
        line = self.reader.readline()
        # 如果是字节序列 +OK,代表执行成功
        if line == b"+OK":
            return True
        else:
            raise Exception(line.decode(self.encoding))

    # 获取键的值
    def get(self, name: str):
        cmd = "*2\r\n$3\r\nGET\r\n" + self.__encode_arg(name)

        self.sock.sendall(cmd.encode(self.encoding))

        # 读取一行
        line = self.reader.readline()
        line = line.decode(self.encoding)

        # 如果是$开头的字符串
        if line.startswith("$"):
            # 截取值的字节个数
            size = int(line[1:])
            # 读取指定个数的字节序列
            data = self.reader.read_full(size)
            # 读取\r\n
            self.reader.readline()
            # 返回解码的字符串结果
            return data.decode(self.encoding)
        else:
            raise Exception(line[1:])

    # 执行命令,把命令分割为参数设置
    def execute(self, *args):
        if len(args) > 0:
            cmd = "*" + str(len(args)) + "\r\n"
            for v in args:
                cmd = cmd + self.__encode_arg(v)

            self.sock.sendall(cmd.encode(self.encoding))
        else:
            raise Exception("command is empty.")

    # 读取结果
    def read_result(self):
        line = self.reader.readline().decode(self.encoding)

        if line:
            if line[0] == "+" or line[0] == ":":
                return line[1:]
            elif line[0] == "$":
                data = self.reader.read_full(int(line[1:]))
                self.reader.readline()
                return data.decode(self.encoding)
            elif line[0] == "*":
                count = int(line[1:])

                data = []
                for k in range(0, count, 1):
                    data.append(self.read_result())
                return data
            else:
                raise Exception(line[1:])
        else:
            raise IOError("sock error")


if __name__ == "__main__":
    con = HRedis()
    con.execute("set", "v123", "小明")
    print("设置结果:", con.read_result())

    con.execute("get", "v123")
    print("键v123的值:", con.read_result())

    con.execute("ping")
    print(con.read_result())

    con.execute("dbsize")
    print("个数:", con.read_result())

执行结果:

六、总结

1、基本能实现set和get命令。

2、读者可以自己实现其他的keys、setnx、strlen、rename、lpush、lpop、rpush、rpop、hset、hget等命令。

相关推荐
SadSunset2 小时前
第一章:Redis 入门介绍
数据库·redis·缓存
电商API&Tina2 小时前
电商数据采集API接口||合规优先、稳定高效、数据精准
java·javascript·数据库·python·json
玲娜贝儿--努力学习买大鸡腿版2 小时前
hot 100 刷题记录(1)
数据结构·python·算法
兮℡檬,3 小时前
答题卡识别判卷
开发语言·python·计算机视觉
阆遤3 小时前
利用TRAE对nanobot进行安全分析并优化
python·安全·ai·trae·nanobot
雕刻刀3 小时前
ERROR: Failed to build ‘natten‘ when getting requirements to build wheel
开发语言·python
何双新3 小时前
Odoo 技术演进全解析:从 Widget 到 Owl,从 Old API 到声明式 ORM
python
山川行4 小时前
关于《项目C语言》专栏的总结
c语言·开发语言·数据结构·vscode·python·算法·visual studio code
星辰徐哥4 小时前
C语言游戏开发:Pygame、SDL、OpenGL深度解析
c语言·python·pygame