当前位置: 首页 > 后端技术 > PHP

用Go实现Redis的第二次client-server交互

时间:2023-03-29 23:15:17 PHP

前面写过。上一篇文章梳理了Godisv1.0版本的基本功能。本文要做的是实现客户端/服务器交互。让代码先运行起来,才有生命力。本文Godis版本号:v0.0.1本系列文章尽量减少对Golang语法、C语言语法和redis原理的介绍,将重点放在“使用Golang实现Redis”这一主题上。如有遗漏或不足,敬请指正。进入正题,Redis事件处理器要实现C/S交互,网络编程必不可少。在Redis中,有以下实现方式:*Redis基于Reactor模型开发了自己的网络事件处理程序:这种处理程序称为文件事件处理程序(fileeventhandler):文件事件处理程序使用I/O多路复用(multiplexing)程序来同时监听多个socket,根据socket当前执行的任务,为socket关联不同的事件处理器。当被监控的套接字准备好执行连接响应(accept)、读(read)、写(write)、关闭(close)等操作时,会产生该操作对应的文件事件,文件事件处理程序会调用与套接字关联的事件处理程序来处理这些事件。*Redis事件处理器架构图:Redis兼顾了简洁性和高性能,但由于其代码的复杂性,Godisv0.0.1将采用更简单的弱智方案。Godis的编码使用了最简单的同步技术,在网络处理上力求简单,因为不会复杂:)。暂时放弃高性能,只为可用性。在以后的版本中,网络层会结合Golang自身的特点进行优化。Godis方案的client/server交互只需要使用Golang的net包进行socket编程,写一个监听-读取-处理-回复server的流程和一个建立-发送-接收-显示client的流程就可以了。图中的英文和英文都是Golang中net包的主要功能。这里简单介绍下本文使用的net包函数:ResolveTCPAdr:Usecase:tcpAddr,err:=net.ResolveTCPAddr("tcp4","127.0.0.1:9736");功能:创建TCPAddr的数据结构类型,包含IP和端口,保留用于连接;ListenTCP:用例:netListen,err:=net.Listen("tcp","127.0.0.1:9763");功能:ListenAccept:Usecase:conn,err:=netListen.Accept()函数:接收请求读:用例:n,err:=conn.Read(buff)函数:读取数据写:用例:conn.Write([]byte(buff))函数:响应datanet.DialTCP:Usecase:conn,err:=net.DialTCP("tcp",nil,tcpAddr)功能:建立连接代码按照上图流程实现部分服务端代码如下:funcmain(){netListen,err:=net.Listen("tcp","127.0.0.1:9736")iferr!=nil{log.Print("listenerr")}//checkError(err)延迟netListen.Close()for{conn,err:=netListen.Accept()iferr!=nil{continue}gohandle(conn)}}真正完整的代码实现见这里。在建立客户端之前,我们使用telnet测试服务器是否可用(Redis也支持Telnet连接和请求,后面协议部分会介绍)。编译godis-server.gogobuildgodis-server.go并启动./godis-server执行telnetlocalhost9736可以看到回复如下:接下来按照简单流程图的步骤,实现客户端如下:funcmain(){IPPort:="127.0.0.1:9736"reader:=bufio.NewReader(os.Stdin)fmt.Println("HiGodis")tcpAddr,err:=net.ResolveTCPAddr("tcp4",IPPort)checkError(err)conn,err:=net.DialTCP("tcp",nil,tcpAddr)checkError(err)deferconn.Close()for{fmt.Print(IPPort+">")文本,_:=reader.ReadString('\n')//清除回车换行text=strings.Replace(text,"\n","",-1)send2Server(text,conn)buff:=make([]byte,1024)n,err:=conn.Read(buff)checkError(err)如果n==0{fmt.Println(IPPort+">","nil")}else{fmt.Println(IPPort+">",string(buff))}}}编译cli.go,启动./cli与服务端进行交互,如下图所示:至此只实现了基本的客户端/服务端通信。接下来给它添加几个交互选项,也就是执行Redis命令行时的选项,让它看起来更像Redis。服务器已添加平滑退出。以后加入持久化后,平滑退出时数据会持久化到磁盘,防止丢失。平滑退出这里使用的方法是让客户端监听信号,当到达“退出”信号时,完成收尾工作后退出。简化代码如下:funcsigHandler(cchanos.Signal){fors:=rangec{switchs{casesyscall.SIGHUP,syscall.SIGINT,syscall.SIGTERM,syscall.SIGQUIT:exitHandler()default:fmt.Println("signal",s)}}}funcexitHandler(){fmt.Println("顺利退出...")fmt.Println("bye")os.Exit(0)}编译后执行Ctrl+c退出,可以看到:本文遇到的问题:1、服务端读取客户端信息时,出现err时没有返回,导致服务端一直读取数据失败。解决方法:return或者其他可以退出goroutine的解决方法。这就是本文的内容。它有点粗糙,但它并非无法使用。丽姐万岁。完整代码请查看本文对应的release,(只有release才是当前文章对应的完整代码,当前repostatus不是)。下一集预告1.[实现get/set命令。][8]