go手写Redis(7)之Handler实现

文章描述了一种TCP服务器处理Resp协议的过程,包括如何创建和管理Connection对象,以及RespHandler如何处理客户端连接。处理器通过协议解析器解析客户端请求,将net.Conn包装成Connection接口,处理数据并返回响应。同时,RespHandler维护一个活动连接池,管理和关闭客户端连接,并与数据库交互执行命令。
摘要由CSDN通过智能技术生成

处理器

在前面协议解析完成之后就需要将协议解析完成的 resp.Reply 结构体交给处理器进行处理了,处理会将 net.Conn 的连接包装成一个 Connection 的接口体交给后续进行使用,然后调用 协议解析器 获取到返回的 chan 管道然后监听管道中的数据进行操作,将对应的响应返回给客户端

Connection

连接对象,用于包装每个客户端的结构体,每个客户端的请求到了 handler 之后都会将其包装为一个 Connection 对象然后保存起来;在 interface/resp/conn.go中进行定义

// Connection 用于封装连接客户端的接口
type Connection interface {
	//Write 写入数据
	Write([]byte) error

	//GetDBIndex 获取到对应的DB数据库
	GetDBIndex() int

	//SelectDB 切换DB
	SelectDB(dbNum int) int
}

conn.go

创建 Connection 的结构体用于包装客户端连接对象

字段说明
  • conn:net包中的连接
  • waitingReply:给客户端回发指令的时候,如果连接被关闭了则需要处理完所有的指令
  • mu:在操作一个客户端连接的时候需要上锁,防止并发出现问题
  • selectedDB:指定当前连接正在操作的数据库
方法说明
  • NewConn:堆外提供的公共方法
  • RemoteAddr:获取到远程客户端的ip地址
  • Close:关闭客户端连接
  • Write:给客户端返回数据
  • GetDBIndex:获取到当前连接操作数据库的索引
  • SelectDB:切换数据库
// Connection 协议层对每一个连接的描述
type Connection struct {
	//客户端的连接信息
	conn net.Conn
	//给客户端回发指令的时候,如果连接被关闭了则需要处理完所有的指令
	waitingReply wait.Wait
	//在操作一个客户端连接的时候需要上锁,防止并发出现问题
	mu sync.Mutex
	//指定当前连接正在操作的数据库
	selectedDB int
}

//NewConn 创建一个新的连接
func NewConn(conn net.Conn) *Connection {
	return &Connection{
		//连接需要新的,其它的参数都用初始化默认的
		conn: conn,
	}
}

// RemoteAddr 获取远程客户端连接的地址
func (c *Connection) RemoteAddr() net.Addr {
	return c.conn.RemoteAddr()
}

// Close 用于关闭连接,
func (c *Connection) Close() error {
	//10秒的超时,等待指令处理完成
	c.waitingReply.WaitWithTimeout(10 * time.Second)
	_ = c.conn.Close()
	return nil
}

//Write 给客户端发送数据
func (c *Connection) Write(bytes []byte) error {
	defer func() {
		c.waitingReply.Done()
		c.mu.Unlock()
	}()
	//空的数组直接返回
	if len(bytes) <= 0 {
		return nil
	}
	//同一个时间只有一个协程能够往客户端发送数据
	c.mu.Lock()
	//表示有一个协程正在发送数据
	c.waitingReply.Add(1)
	_, err := c.conn.Write(bytes)
	return err
}

// GetDBIndex 获取到db的索引
func (c *Connection) GetDBIndex() int {
	return c.selectedDB
}

// SelectDB 选择db
func (c *Connection) SelectDB(dbNum int) int {
	c.selectedDB = dbNum
	return c.selectedDB
}

Handler

前面已经说了,在 interface/tcp/handler.go 中创建了一个处理器的顶级接口,里面定义了两个方法

  • Handler:ctx传递上下文对象,conn net包中的连接对象
  • Close:关闭方法
// Handler tcp抽象出处理函数接口
type Handler interface {
	//Handler ctx上下文,conn tcp连接
	Handler(ctx context.Context, conn net.Conn)

	//Close 关闭
	Close() error
}

RespHandler

resp协议的处理实体类,在 resp/handler/handler.go 中定义

handler.go

字段说明
  • activeConn:保存所有的客户端连接 sync.Map 包中线程安全的 Map
  • db:自定义的数据库层实现,后续再实现
  • closing:是否关闭
方法说明
  • MakeHandler:对外提供创建 RespHandler 的方法
  • closeClient:关闭客户端
  • Handler:处理方法,前面tcp服务接受到连接之后会将对应的请求交给 Handler 进行处理,handler然后去调用 parser.ParseStream() 前面实现的协议解析进行解析,获取到返回的 chan 管道然后循环处理获取到的 Payload
  • Close:关闭handler
// RespHandler 处理Resp协议的处理器
type RespHandler struct {
	//保存所有的连接
	activeConn sync.Map
	//数据库核心业务层
	db databaseface.Database
	//是否正在关闭
	closing atomic.Boolean
}

//MakeHandler 创建一个handler
func MakeHandler() *RespHandler {
	var db databaseface.Database
	db = database.NewDatabase()
	return &RespHandler{
		db: db,
	}
}

//closeClient 关闭单个客户端
func (r *RespHandler) closeClient(client *connection.Connection) {
	//客户端连接进行关闭
	_ = client.Close()
	//客户端连接关闭时进行一些善后的工作
	r.db.AfterClientClose(client)
	//删除客户端
	r.activeConn.Delete(client)
}

//Handler 进行处理
func (r *RespHandler) Handler(ctx context.Context, conn net.Conn) {
	if r.closing.Get() {
		//判断是否正在关闭
		_ = conn.Close()
	}
	client := connection.NewConn(conn)
	//暂时用空结构体,value就不会占用空间,map就成为了一个Set结构
	r.activeConn.Store(client, struct{}{})
	//将连接发送给parser进行tcp报文解析
	ch := parser.ParseStream(conn)
	//相当于死循环,一直获取到解析出来的数据
	for payload := range ch {
		err := payload.Err
		//错误的逻辑
		if err != nil {
			//说明客户端给我们发送的是四次挥手需要断开连接,或者使用的是一个被关闭的连接
			if err == io.EOF ||
				err == io.ErrUnexpectedEOF ||
				strings.Contains(err.Error(), "use of closed network connection") {
				r.closeClient(client)
				logger.Info("Connection closed:" + client.RemoteAddr().String())
			}
			//协议的错误
			errReply := reply.MakeStandardErrReply(err.Error())
			//回写给客户端错误的数据信息
			err := client.Write(errReply.ToBytes())
			//如果回写出错,直接关闭客户端
			if err != nil {
				r.closeClient(client)
				logger.Info("Connection closed:" + client.RemoteAddr().String())
			}
			//继续执行
			continue
		}
		//执行正常的请求逻辑
		data := payload.Data
		if data == nil {
			continue
		}
		multiBulkReply, ok := data.(*reply.MultiBulkReply)
		if !ok {
			logger.Error("require multi bulk reply............")
			continue
		}
		//通过内核进行执行指令
		result := r.db.Exec(client, multiBulkReply.Args)
		if result != nil {
			_ = client.Write(result.ToBytes())
		} else {
			_ = client.Write(reply.MakeUnknownErrReply().ToBytes())
		}
	}
}

//Close 关闭客户端
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()
		r.db.AfterClientClose(client)
		//返回true才会下一个遍历
		return true
	})
	r.db.Close()
	return nil
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值