runningwater пре 5 месеци
родитељ
комит
23a8a0df6e

+ 59 - 0
.golangci.yaml

@@ -0,0 +1,59 @@
+version: "2"
+linters:
+  settings:
+    assailant:
+      # To specify a set of function names to exclude.
+      # The values are merged with the builtin exclusions.
+      # The builtin exclusions can be disabled by setting `use-builtin-exclusions` to `false`.
+      # Default: ["^(fmt|log|logger|t|)\.(Print|Sprint|Sprint|Fatal|Panic|Error|Warn|Warning|Info|Debug|Log)(|f|ln)$"]
+      exclude:
+        - Append
+        - \.Wrap
+      use-builtin-exclusions: false
+    laparoscopy:
+      # Check all assigning the loop variable to another variable.
+      # Default: false
+      check-alias: true
+    decoder:
+      dec-order:
+        - type
+        - const
+        - var
+        - func
+      # If true, underscore vars (vars with "_" as the name) will be ignored at all checks.
+      # Default: false (underscore vars are not ignored)
+      ignore-underscore-vars: false
+      # If true, order of declarations is not checked at all.
+      # Default: true (disabled)
+      disable-dec-order-check: false
+      # If true, `init` func can be anywhere in file (does not have to be declared before all other functions).
+      # Default: true (disabled)
+      disable-init-func-first-check: false
+      # If true, multiple global `type`, `const` and `var` declarations are allowed.
+      # Default: true (disabled)
+      disable-dec-num-check: false
+      # If true, type declarations will be ignored for dec num check.
+      # Default: false (type statements are not ignored)
+      disable-type-dec-num-check: false
+      # If true, const declarations will be ignored for dec num check.
+      # Default: false (const statements are not ignored)
+      disable-const-dec-num-check: false
+      # If true, var declarations will be ignored for dec num check.
+      # Default: false (var statements are not ignored)
+      disable-var-dec-num-check: false
+
+formatters:
+  settings:
+    gofmt:
+      simplify: false
+      rewrite-rules:
+        - pattern: 'interface{}'
+          replacement: 'any'
+        - pattern: 'a[b:len(a)]'
+          replacement: 'a[b:]'
+    lines:
+      max-len: 200
+      tab-len: 8
+      shorten-comments: true
+      reformat-tags: false
+      chain-split-dots: false

+ 15 - 0
.vscode/launch.json

@@ -0,0 +1,15 @@
+{
+  // Use IntelliSense to learn about possible attributes.
+  // Hover to view descriptions of existing attributes.
+  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+  "version": "0.2.0",
+  "configurations": [
+    {
+      "name": "Launch Package",
+      "type": "go",
+      "request": "launch",
+      "mode": "auto",
+      "program": "${fileDirname}/main.go"
+    }
+  ]
+}

+ 7 - 2
config/config.go

@@ -82,7 +82,7 @@ func parse(src io.Reader) *ServerProperties {
 					fieldVal.SetInt(intValue)
 				}
 			case reflect.Bool:
-				boolValue := "yes" == value
+				boolValue := value == "yes"
 				fieldVal.SetBool(boolValue)
 			case reflect.Slice:
 				if field.Type.Elem().Kind() == reflect.String {
@@ -103,6 +103,11 @@ func SetupConfig(configFilename string) {
 	if err != nil {
 		panic(err)
 	}
-	defer file.Close()
+	defer func() {
+		closeErr := file.Close()
+		if closeErr != nil {
+			panic(closeErr)
+		}
+	}()
 	Properties = parse(file)
 }

+ 29 - 0
database/echo_database.go

@@ -0,0 +1,29 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/12 14:57
+// Desc:
+
+package database
+
+import (
+	"github.com/runningwater/go-redis/interface/resp"
+	"github.com/runningwater/go-redis/lib/logger"
+	"github.com/runningwater/go-redis/resp/reply"
+)
+
+type EchoDatabase struct {
+}
+
+func NewEchoDatabase() *EchoDatabase {
+	return &EchoDatabase{}
+}
+func (e *EchoDatabase) Exec(_ resp.Connection, args [][]byte) resp.Reply {
+	return reply.NewMultiBulkReply(args)
+}
+
+func (e *EchoDatabase) Close() {
+	logger.Info("database closed")
+}
+
+func (e *EchoDatabase) AfterClientClose(_ resp.Connection) {
+	logger.Info("client closed, AfterClientClose")
+}

+ 0 - 11
go.sum

@@ -1,11 +0,0 @@
-github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
-github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
-github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
-golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=

+ 21 - 0
interface/database/database.go

@@ -0,0 +1,21 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/12 13:27
+// Desc: Redis 数据库业务操作
+
+package database
+
+import (
+	"github.com/runningwater/go-redis/interface/resp"
+)
+
+type CmdLine = [][]byte
+
+type DataEntity struct {
+	Data any
+}
+
+type Database interface {
+	Exec(client resp.Connection, args [][]byte) resp.Reply
+	Close()
+	AfterClientClose(client resp.Connection)
+}

+ 2 - 0
interface/resp/reply.go

@@ -8,4 +8,6 @@ package resp
 type Reply interface {
 	// ToBytes 将响应对象转换为符合Redis协议的字节数组
 	ToBytes() []byte
+	// String 返回响应对象的字符串表示
+	String() string
 }

+ 2 - 2
lib/logger/files.go

@@ -16,7 +16,7 @@ func checkPermission(src string) bool {
 }
 
 func isNotExistMkDir(src string) error {
-	if notExist := checkNotExist(src); notExist == true {
+	if notExist := checkNotExist(src); notExist {
 		if err := mkDir(src); err != nil {
 			return err
 		}
@@ -35,7 +35,7 @@ func mkDir(src string) error {
 
 func mustOpen(fileName, dir string) (*os.File, error) {
 	perm := checkPermission(dir)
-	if perm == true {
+	if perm {
 		return nil, fmt.Errorf("permission denied dir: %s", dir)
 	}
 

+ 3 - 1
main.go

@@ -10,6 +10,7 @@ import (
 
 	"github.com/runningwater/go-redis/config"
 	"github.com/runningwater/go-redis/lib/logger"
+	"github.com/runningwater/go-redis/resp/handler"
 	"github.com/runningwater/go-redis/tcp"
 )
 
@@ -44,7 +45,8 @@ func main() {
 		&tcp.Config{
 			Address: fmt.Sprintf("%s:%d", config.Properties.Bind, config.Properties.Port),
 		},
-		tcp.NewEcho(),
+		// tcp.NewEchoHandler(),
+		handler.NewHandler(),
 	)
 	if err != nil {
 		logger.Error(err)

+ 65 - 0
resp/connection/conn.go

@@ -0,0 +1,65 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/12 11:02
+// Desc: 对每个连接进行封装(第个客服连接的描述)
+
+package connection
+
+import (
+	"net"
+	"sync"
+	"time"
+
+	"github.com/runningwater/go-redis/lib/logger"
+	"github.com/runningwater/go-redis/lib/sync/wait"
+)
+
+type Connection struct {
+	conn       net.Conn
+	waiting    wait.Wait
+	mu         sync.Mutex
+	selectedDB int
+}
+
+func NewConnection(conn net.Conn) *Connection {
+	return &Connection{
+		conn: conn,
+	}
+}
+
+func (c *Connection) RemoteAddr() net.Addr {
+	return c.conn.RemoteAddr()
+}
+
+func (c *Connection) Close() error {
+	timeout := c.waiting.WaitWithTimeout(10 * time.Second)
+	if timeout {
+		logger.Info("timeout to close connection")
+	} else {
+		logger.Info("connection closed")
+	}
+	err := c.conn.Close()
+	return err
+}
+
+func (c *Connection) Write(bytes []byte) error {
+	if len(bytes) == 0 {
+		return nil
+	}
+	logger.InfoC("write to client: ", string(bytes))
+	c.mu.Lock()
+	c.waiting.Add(1)
+	defer func() {
+		c.waiting.Done()
+		c.mu.Unlock()
+	}()
+	_, err := c.conn.Write(bytes)
+	return err
+}
+
+func (c *Connection) GetDBIndex() int {
+	return c.selectedDB
+}
+
+func (c *Connection) SelectDB(dbNum int) {
+	c.selectedDB = dbNum
+}

+ 125 - 0
resp/handler/handler.go

@@ -0,0 +1,125 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/9/12 13:24
+// Desc: 处理 RESP 协议
+
+package handler
+
+import (
+	"context"
+	"errors"
+	"io"
+	"net"
+	"strings"
+	"sync"
+
+	"github.com/runningwater/go-redis/database"
+	dbface "github.com/runningwater/go-redis/interface/database"
+	"github.com/runningwater/go-redis/lib/logger"
+	"github.com/runningwater/go-redis/lib/sync/atomic"
+	"github.com/runningwater/go-redis/resp/connection"
+	"github.com/runningwater/go-redis/resp/parser"
+	"github.com/runningwater/go-redis/resp/reply"
+)
+
+type RespHandler struct {
+	activeConn sync.Map
+	db         dbface.Database
+	closing    atomic.Boolean
+}
+
+func NewHandler() *RespHandler {
+	var db dbface.Database
+	db = database.NewEchoDatabase()
+
+	return &RespHandler{
+		db: db,
+	}
+}
+
+func (r *RespHandler) Handle(ctx context.Context, conn net.Conn) {
+	if r.closing.Get() {
+		_ = conn.Close()
+	}
+	logger.Info("new connection from ", conn.RemoteAddr().String())
+	client := connection.NewConnection(conn)
+	r.activeConn.Store(client, struct{}{})
+
+	// 解析数据
+	ch := parser.ParseStream(conn)
+	for payload := range ch {
+		logger.Info("收到 通道数据: ", payload)
+		// error
+		if payload.Err != nil {
+			if errors.Is(payload.Err, io.EOF) ||
+				errors.Is(payload.Err, io.ErrUnexpectedEOF) ||
+				strings.Contains(payload.Err.Error(), "use of closed network connection") {
+				r.closeClient(client)
+				logger.Info("connection closed: ", client.RemoteAddr().String())
+				return
+			}
+			// 协议错误
+			logger.Error("协议错误: ", payload.Err.Error())
+			errReplay := reply.NewErrReply(payload.Err.Error())
+
+			err := client.Write(errReplay.ToBytes())
+			if err != nil {
+				r.closeClient(client)
+				logger.Error("client.Write:", err.Error())
+				return
+			}
+			continue
+		}
+
+		// exec
+		if payload.Data == nil {
+			logger.Error("empty payload")
+			continue
+		}
+		bulkReply, ok := payload.Data.(*reply.MultiBulkReply)
+		if !ok {
+			logger.Error("require multi bulk reply")
+			continue
+		}
+		// 命令执行
+		execResult := r.db.Exec(client, bulkReply.Args)
+		if execResult == nil {
+			execResult = reply.NewUnknownErrReply()
+		}
+		// 返回结果
+		logger.Info("exec result: ", execResult)
+
+		_ = client.Write(execResult.ToBytes())
+
+	} //  for end
+}
+
+// Close 关闭RespHandler处理器,释放相关资源
+// 该函数会关闭所有活跃连接并关闭数据库连接
+// 返回值: error - 关闭操作的错误信息,目前始终返回nil
+func (r *RespHandler) Close() error {
+	logger.Info("handler shutting down...")
+	r.closing.Set(true)
+
+	// 遍历所有活跃连接并关闭它们
+	r.activeConn.Range(
+		func(key, value any) bool {
+			client := key.(*connection.Connection)
+			_ = client.Close()
+			return true
+		})
+
+	// 关闭数据库连接
+	r.db.Close()
+
+	return nil
+}
+
+// closeClient 关闭客户端连接
+// 参数:
+//
+//	client *connection.Connection - 需要关闭的客户端连接对象
+func (r *RespHandler) closeClient(client *connection.Connection) {
+	_ = client.Close()
+	r.db.AfterClientClose(client)
+	r.activeConn.Delete(client)
+}

+ 320 - 0
resp/parser/parser.go

@@ -0,0 +1,320 @@
+// Author: simon (ynwdlxm@163.com)
+// Date: 2025/7/11 15:58
+// Desc: 解析器
+//
+// 解析器用于将Redis协议的字节流解析为抽象的响应对象
+
+package parser
+
+import (
+	"bufio"
+	"errors"
+	"fmt"
+	"io"
+	"runtime/debug"
+	"strconv"
+	"strings"
+
+	"github.com/runningwater/go-redis/interface/resp"
+	"github.com/runningwater/go-redis/lib/logger"
+	"github.com/runningwater/go-redis/resp/reply"
+)
+
+// Payload 解析器的载荷
+// 用于存储解析器的状态信息
+type Payload struct {
+	Data resp.Reply // 解析后的响应对象, 与返回数据结构一致
+	Err  error      // 解析过程中发生的错误
+}
+
+func (p *Payload) String() string {
+	return fmt.Sprintf("payload: %s, err: %v", p.Data, p.Err)
+}
+
+type currentState struct {
+	readingMultiLine  bool     // 是否正在读取多行数据
+	expectedArgsCount int      // 期望的参数数量
+	msgType           byte     // 消息类型
+	args              [][]byte // 参数列表
+	bulkLen           int64    // 批量数据的长度
+}
+
+func (s *currentState) finished() bool {
+	return s.expectedArgsCount > 0 && len(s.args) == s.expectedArgsCount
+}
+
+// ParseStream 解析输入流中的数据
+// 解析器会根据Redis协议的规则解析输入流中的数据
+// 解析后的结果会通过通道发送给调用方
+// 参数:
+//
+//	reader io.Reader - 输入流读取器
+//
+// 返回值:
+//
+//	<-chan *Payload - 解析结果通道,每个元素都是一个解析后的Payload对象
+func ParseStream(reader io.Reader) <-chan *Payload {
+	logger.Debug("parse stream...")
+	ch := make(chan *Payload)
+	go parse0(reader, ch)
+	return ch
+}
+
+func parse0(reader io.Reader, ch chan<- *Payload) {
+	defer func() {
+		if err := recover(); err != nil {
+			logger.Error(string(debug.Stack()))
+		}
+	}()
+	logger.Debug("parse0 ...")
+	bufReader := bufio.NewReader(reader)
+	var state currentState
+	var err error
+	var line []byte // 一行数据
+	for {           // 死循环
+		var ioErr bool
+		line, ioErr, err = readLine(bufReader, &state)
+		if err != nil {
+			if ioErr {
+				ch <- &Payload{
+					Err: err,
+				}
+				close(ch)
+				return
+			}
+			ch <- &Payload{
+				Err: err,
+			}
+			state = currentState{} // 重置状态
+			continue
+		}
+
+		// 判断是不是多行模式
+		if !state.readingMultiLine {
+			if line[0] == '*' { // *3/r/n
+				err = parseMultiBulkHeader(line, &state)
+				if err != nil {
+					ch <- &Payload{
+						Err: errors.New("protocol error: " + string(line)),
+					}
+					state = currentState{} // 重置状态
+					continue
+				}
+				if state.expectedArgsCount == 0 {
+					ch <- &Payload{
+						Data: reply.NewEmptyMultiBulkReply(),
+					}
+					state = currentState{} // 重置状态
+					continue
+				}
+			} else if line[0] == '$' { // $3/r/n
+				err = parseBulkHeader(line, &state)
+				if err != nil {
+					ch <- &Payload{
+						Err: errors.New("protocol error: " + string(line)),
+					}
+					state = currentState{} // 重置状态
+					continue
+				}
+				// $-1\r\n
+				if state.bulkLen == -1 {
+					ch <- &Payload{
+						Data: reply.NewNullBulkReply(),
+					}
+					state = currentState{} // 重置状态
+					continue
+				}
+			} else {
+				var result resp.Reply
+				result, err = parseSingleLineReply(line)
+				ch <- &Payload{
+					Data: result,
+					Err:  err,
+				}
+				state = currentState{}
+				continue
+			}
+		} else {
+			// 解析多行模式
+			err = readBody(line, &state)
+			if err != nil {
+				ch <- &Payload{
+					Err: errors.New("protocol error: " + string(line)),
+				}
+				state = currentState{}
+				continue
+			}
+			if state.finished() {
+				var result resp.Reply
+				switch state.msgType {
+				case '*':
+					result = reply.NewMultiBulkReply(state.args)
+				case '$':
+					result = reply.NewBulkReply(state.args[0])
+				}
+				ch <- &Payload{
+					Data: result,
+					Err:  err,
+				}
+				state = currentState{} // 重置状态
+			}
+		}
+	} // End for true
+}
+
+// 读取一行数据
+// 读取到的数据可能是完整的一行,也可能是不完整的一行
+//
+//	返回值:
+//	1. 读取到的数据
+//	2. 是否读取到了不完整的一行
+//	3. 错误信息
+//	例如: *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
+//	读取到的数据可能是: *3\r\n, $3\r\nSET\r\n, $3\r\nkey\r\n, $5\r\nvalue\r\n
+//	读取到的数据可能是: *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
+func readLine(reader *bufio.Reader, state *currentState) ([]byte, bool, error) {
+	var msg []byte
+	var err error
+	if state.bulkLen == 0 { // \r\n 切分
+		// 读取一行数据
+		reader.Reset(reader)
+		msg, err = reader.ReadBytes('\n')
+		logger.Info("\r\n***readLine: ", string(msg))
+		if err != nil {
+			return nil, true, err
+		}
+		// 不是 \r\n 结尾的数据
+		if len(msg) == 0 || msg[len(msg)-2] != '\r' {
+			return nil, false, errors.New("readLine-protocol error: " + string(msg))
+		}
+
+	} else {
+		// ELSE 之前读取到了 $数字,严格读取数字个字节
+		msg = make([]byte, state.bulkLen+2)
+		_, err := io.ReadFull(reader, msg)
+		if err != nil {
+			return nil, true, err
+		}
+		// 不是 \r\n 结尾的数据
+		if len(msg) == 0 || msg[len(msg)-2] != '\r' || msg[len(msg)-1] != '\n' {
+			return nil, false, errors.New("readLine-protocol error: " + string(msg))
+		}
+
+		// 重置 bulkLen
+		state.bulkLen = 0
+	}
+	return msg, false, nil
+}
+
+// 解析串的头部信息
+//
+//	例如: *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
+//	解析为: *3\r\n
+func parseMultiBulkHeader(msg []byte, state *currentState) error {
+	var err error
+	var expectedLine uint64
+	expectedLine, err = strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)
+	if err != nil {
+		return errors.New("protocol error: " + string(msg))
+	}
+	if expectedLine == 0 {
+		state.expectedArgsCount = 0
+		return nil
+	} else if expectedLine > 0 {
+		state.msgType = msg[0]
+		state.readingMultiLine = true
+		state.expectedArgsCount = int(expectedLine)
+		state.args = make([][]byte, 0, expectedLine)
+		return nil
+	} else {
+		return errors.New("protocol error: " + string(msg))
+	}
+}
+
+// 单行字符串
+//
+//	$4\r\nPING\r\n
+func parseBulkHeader(msg []byte, state *currentState) error {
+	var err error
+	// $ 开头的行,读取 bulkLen
+	state.bulkLen, err = strconv.ParseInt(string(msg[1:len(msg)-2]), 10, 64)
+	if err != nil {
+		return errors.New("protocol error: " + string(msg))
+	}
+
+	if state.bulkLen == -1 {
+		return nil
+	} else if state.bulkLen > 0 {
+		state.msgType = msg[0] // $
+		state.readingMultiLine = true
+		state.expectedArgsCount = 1
+		state.args = make([][]byte, 0, 1)
+		return nil
+	} else {
+		return errors.New("protocol error: " + string(msg))
+	}
+}
+
+// 解析单行回复
+//
+//	例如:
+//	+OK\r\n
+//	-err\r\n
+//	:1\r\n
+func parseSingleLineReply(msg []byte) (resp.Reply, error) {
+	str := strings.TrimSuffix(string(msg), "\r\n") // 去除 \r\n
+
+	var result resp.Reply
+	switch msg[0] {
+	case '+':
+		result = reply.NewStatusReply(str[1:])
+	case '-':
+		result = reply.NewErrReply(str[1:])
+	case ':':
+		val, err := strconv.ParseInt(str[1:], 10, 64)
+		if err != nil {
+			return nil, errors.New("protocol error: " + string(msg))
+		}
+		result = reply.NewIntReply(val)
+	default:
+		return nil, errors.New("protocol error: " + string(msg))
+	}
+	return result, nil
+}
+
+// readBody 解析Redis协议中的消息体部分,处理以$开头的bulk字符串或普通字符串
+//
+//	例如:
+//	 PING\r\n
+//	 SET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n
+//
+// 参数:
+//
+//	msg: 完整的消息字节切片,包含\r\n结尾
+//	state: 当前解析状态的指针,用于存储解析结果
+//
+// 返回值:
+//
+//	error: 解析过程中出现的错误,如协议格式错误等
+func readBody(msg []byte, state *currentState) error {
+	line := msg[:len(msg)-2] // 去除 \r\n
+
+	var err error
+
+	// 处理以$开头的bulk字符串格式
+	if line[0] == '$' {
+		state.bulkLen, err = strconv.ParseInt(string(line[1:]), 10, 64)
+		if err != nil {
+			return errors.New("protocol error: " + string(msg))
+		}
+		// 处理空字符串情况,$0\r\n表示空字符串
+		if state.bulkLen <= 0 {
+			state.args = append(state.args, []byte{})
+			state.bulkLen = 0
+		}
+	} else {
+		// 处理普通字符串,直接添加到参数列表中
+		state.args = append(state.args, line)
+	}
+	return nil
+}

+ 20 - 0
resp/reply/consts.go

@@ -7,6 +7,10 @@ package reply
 // PongReply 表示PONG响应,用于Redis协议的PONG响应
 type PongReply struct{}
 
+func (p PongReply) String() string {
+	return "+PONG\r\n"
+}
+
 // ToBytes 将PONG响应转换为字节数组
 // 返回格式:"+PONG\r\n"
 func (p PongReply) ToBytes() []byte {
@@ -22,6 +26,10 @@ func NewPongReply() *PongReply {
 // 返回格式:"+OK\r\n"
 type OkReply struct{}
 
+func (o OkReply) String() string {
+	return "+OK\r\n"
+}
+
 // ToBytes 将OK响应转换为字节数组
 // 返回格式:"+OK\r\n"
 func (o OkReply) ToBytes() []byte {
@@ -36,6 +44,10 @@ func NewOkReply() *OkReply {
 // NoReply 表示NO响应,用于Redis协议的NO响应
 type NoReply struct{}
 
+func (n NoReply) String() string {
+	return ""
+}
+
 // ToBytes 将NO响应转换为字节数组
 func (n NoReply) ToBytes() []byte {
 	return []byte("")
@@ -50,6 +62,10 @@ func NewNoReply() *NoReply {
 // 返回格式:"$-1\r\n"
 type NullBulkReply struct{}
 
+func (n NullBulkReply) String() string {
+	return "$-1\r\n"
+}
+
 // ToBytes 将NO响应转换为字节数组
 // 返回格式:"$-1\r\n"
 func (n NullBulkReply) ToBytes() []byte {
@@ -63,6 +79,10 @@ func NewNullBulkReply() *NullBulkReply {
 // 返回格式:"*0\r\n"
 type EmptyMultiBulkReply struct{}
 
+func (e EmptyMultiBulkReply) String() string {
+	return "*0\r\n"
+}
+
 // ToBytes 将空的多批量回复转换为字节数组
 func (e EmptyMultiBulkReply) ToBytes() []byte {
 	return []byte("*0\r\n")

+ 17 - 3
resp/reply/error.go

@@ -11,6 +11,12 @@ package reply
 // 当没有更具体的错误类型可用时使用
 type UnknownErrReply struct{}
 
+func NewUnknownErrReply() *UnknownErrReply {
+	return &UnknownErrReply{}
+}
+func (u *UnknownErrReply) String() string {
+	return u.Error()
+}
 func (u *UnknownErrReply) Error() string {
 	return "Err unknown"
 }
@@ -19,7 +25,8 @@ func (u *UnknownErrReply) ToBytes() []byte {
 	return []byte("-Err unknown\r\n")
 }
 
-// ArgNumErrReply represents an error reply for wrong number of arguments in Redis protocol.
+// ArgNumErrReply 表示命令参数数量错误的响应
+// 当命令的参数数量不符合预期时使用
 type ArgNumErrReply struct {
 	Cmd string
 }
@@ -32,12 +39,15 @@ func (a *ArgNumErrReply) ToBytes() []byte {
 	return []byte("-ERR wrong number of arguments for '" + a.Cmd + "' command\r\n")
 }
 
+// NewArgNumErrReply 创建指定命令的参数数量错误响应
 func NewArgNumErrReply(cmd string) *ArgNumErrReply {
 	return &ArgNumErrReply{
 		Cmd: cmd,
 	}
 }
 
+// SyntaxErrReply 表示命令语法错误的通用响应
+// 对应Redis的"syntax error"响应
 type SyntaxErrReply struct{}
 
 func (s *SyntaxErrReply) Error() string {
@@ -52,20 +62,24 @@ func NewSyntaxErrReply() *SyntaxErrReply {
 	return &SyntaxErrReply{}
 }
 
+// WrongTypeErrReply 表示对键值进行类型不匹配操作的错误
+// 对应Redis的 "WRONGTYPE" 错误响应
 type WrongTypeErrReply struct{}
 
 func (w *WrongTypeErrReply) Error() string {
-	return "WRONGTYPE Operation against a key holding the wrong kind of value"
+	return "WRONG TYPE Operation against a key holding the wrong kind of value"
 }
 
 func (w *WrongTypeErrReply) ToBytes() []byte {
-	return []byte("-WRONGTYPE Operation against a key holding the wrong kind of value\r\n")
+	return []byte("-WRONG TYPE Operation against a key holding the wrong kind of value\r\n")
 }
 
 func NewWrongTypeErrReply() *WrongTypeErrReply {
 	return &WrongTypeErrReply{}
 }
 
+// ProtocolErrReply 表示违反RESP协议规范的错误
+// Msg 包含具体的协议违规详情
 type ProtocolErrReply struct {
 	Msg string
 }

+ 38 - 2
resp/reply/reply.go

@@ -39,6 +39,10 @@ type BulkReply struct {
 	Arg []byte // "test" => "$4\r\ntest\r\n"
 }
 
+func (b *BulkReply) String() string {
+	return string(b.ToBytes())
+}
+
 func (b *BulkReply) ToBytes() []byte {
 	if len(b.Arg) == 0 {
 		return nullBulkReplyBytes
@@ -50,11 +54,15 @@ func NewBulkReply(arg []byte) *BulkReply {
 	return &BulkReply{Arg: arg}
 }
 
-// MultiBulkReply 表示多条批量字符串回复的结构体。
+// MultiBulkReply 表示多条批量字符串回复的结构体。 数组
 type MultiBulkReply struct {
 	Args [][]byte
 }
 
+func (m *MultiBulkReply) String() string {
+	return string(m.ToBytes())
+}
+
 func (m *MultiBulkReply) ToBytes() []byte {
 	argLen := len(m.Args)
 	var buf bytes.Buffer
@@ -74,10 +82,18 @@ func NewMultiBulkReply(args [][]byte) *MultiBulkReply {
 	return &MultiBulkReply{Args: args}
 }
 
+// StatusReply 表示简单字符串回复的结构体。
+// 例如:OK、PING、SET、GET等命令的回复。
+// 对应Redis的"+"开头的回复。
+// 例如:"+OK\r\n"
 type StatusReply struct {
 	Status string
 }
 
+func (s *StatusReply) String() string {
+	return string(s.ToBytes())
+}
+
 func (s *StatusReply) ToBytes() []byte {
 	return []byte("+" + s.Status + CRLF)
 }
@@ -86,13 +102,23 @@ func NewStatusReply(status string) *StatusReply {
 	return &StatusReply{Status: status}
 }
 
+// IntReply 表示整数回复的结构体。
+// 对应Redis的":"开头的回复。
+// 例如:":1\r\n"
 type IntReply struct {
 	Code int64
 }
 
+func (i *IntReply) String() string {
+	return string(i.ToBytes())
+}
+
 func (i *IntReply) ToBytes() []byte {
 	return []byte(":" + strconv.FormatInt(i.Code, 10) + CRLF)
 }
+func NewIntReply(code int64) *IntReply {
+	return &IntReply{Code: code}
+}
 
 // ErrorReply 表示错误回复的接口。
 type ErrorReply interface {
@@ -100,10 +126,17 @@ type ErrorReply interface {
 	ToBytes() []byte
 }
 
+// StandardErrReply 表示标准错误回复的结构体。
+// 对应Redis的"-"开头的回复。
+// 例如:"-ERR unknown command 'foobar'\r\n"
 type StandardErrReply struct {
 	Status string
 }
 
+func (s *StandardErrReply) String() string {
+	return string(s.ToBytes())
+}
+
 func (s *StandardErrReply) Error() string {
 	return s.Status
 }
@@ -112,10 +145,13 @@ func (s *StandardErrReply) ToBytes() []byte {
 	return []byte("-" + s.Status + CRLF)
 }
 
-func NewStandardErrReply(status string) *StandardErrReply {
+func NewErrReply(status string) *StandardErrReply {
 	return &StandardErrReply{Status: status}
 }
 
+// IsErrReply 判断回复是否为错误回复。
+// 错误回复的第一个字节为"-"。
+// 例如:"-ERR unknown command 'foobar'\r\n"
 func IsErrReply(reply resp.Reply) bool {
 	return reply.ToBytes()[0] == '-'
 }

+ 3 - 2
tcp/echo.go

@@ -34,7 +34,7 @@ type EchoHandler struct {
 	closing    atomic.Boolean
 }
 
-func NewEcho() *EchoHandler {
+func NewEchoHandler() *EchoHandler {
 	return &EchoHandler{
 		activeConn: sync.Map{},
 		closing:    0,
@@ -63,9 +63,10 @@ func (e *EchoHandler) Handle(_ctx context.Context, conn net.Conn) {
 			}
 			return
 		}
-
+		msg = "Echo: " + msg
 		client.Waiting.Add(1)
 		b := []byte(msg)
+
 		_, _ = conn.Write(b)
 		client.Waiting.Done()
 	}

+ 2 - 2
tcp/server.go

@@ -19,8 +19,8 @@ type Config struct {
 func ListenAndServeWithSignal(cfg *Config, handler tcp.Handler) error {
 
 	closeChan := make(chan struct{})
-	sigChan := make(chan os.Signal)
-	signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
+	sigChan := make(chan os.Signal, 1)
+	signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT) //nolint:govt
 	go func() {
 		sig := <-sigChan
 		switch sig {