- 2.Zinx-V0.2-简单的连接封装与业务绑定
- 2.1 Zinx-V0.2代码实现
- A) ziface创建iconnection.go
- B) znet 创建iconnection.go
- C) 重新更正一下Server.go中 处理conn的连接业务
- 2.2 使用Zinx-V0.2完成应用程序
- 2.1 Zinx-V0.2代码实现
2.Zinx-V0.2-简单的连接封装与业务绑定
V0.1版本我们已经实现了一个基础的Server框架,现在我们需要对客户端链接和不同的客户端链接所处理的不同业务再做一层接口封装,当然我们先是把架构搭建起来。
现在在ziface下创建一个属于链接的接口文件iconnection.go,当然他的实现文件我们放在znet下的connection.go中。
2.1 Zinx-V0.2代码实现
A) ziface创建iconnection.go
zinx/ziface/iconnection.go
package zifaceimport "net"//定义连接接口type IConnection interface {//启动连接,让当前连接开始工作Start()//停止连接,结束当前连接状态MStop()//从当前连接获取原始的socket TCPConnGetTCPConnection() *net.TCPConn//获取当前连接IDGetConnID() uint32//获取远程客户端地址信息RemoteAddr() net.Addr}//定义一个统一处理链接业务的接口type HandFunc func(*net.TCPConn, []byte, int) error
该接口的一些基础方法,代码注释已经介绍的很清楚,这里先简单说明一个HandFunc这个函数类型,这个是所有conn链接在处理业务的函数接口,第一参数是socket原生链接,第二个参数是客户端请求的数据,第三个参数是客户端请求的数据长度。这样,如果我们想要指定一个conn的处理业务,只要定义一个HandFunc类型的函数,然后和该链接绑定就可以了。
B) znet 创建iconnection.go
zinx/znet/connection.go
package znetimport ("fmt""net""zinx/ziface")type Connection struct {//当前连接的socket TCP套接字Conn *net.TCPConn//当前连接的ID 也可以称作为SessionID,ID全局唯一ConnID uint32//当前连接的关闭状态isClosed bool//该连接的处理方法apihandleAPI ziface.HandFunc//告知该链接已经退出/停止的channelExitBuffChan chan bool}//创建连接的方法func NewConntion(conn *net.TCPConn, connID uint32, callback_api ziface.HandFunc) *Connection{c := &Connection{Conn: conn,ConnID: connID,isClosed: false,handleAPI: callback_api,ExitBuffChan: make(chan bool, 1),}return c}/* 处理conn读数据的Goroutine */func (c *Connection) StartReader() {fmt.Println("Reader Goroutine is running")defer fmt.Println(c.RemoteAddr().String(), " conn reader exit!")defer c.Stop()for {//读取我们最大的数据到buf中buf := make([]byte, 512)cnt, err := c.Conn.Read(buf)if err != nil {fmt.Println("recv buf err ", err)c.ExitBuffChan <- truecontinue}//调用当前链接业务(这里执行的是当前conn的绑定的handle方法)if err := c.handleAPI(c.Conn, buf, cnt); err !=nil {fmt.Println("connID ", c.ConnID, " handle is error")c.ExitBuffChan <- truereturn}}}//启动连接,让当前连接开始工作func (c *Connection) Start() {//开启处理该链接读取到客户端数据之后的请求业务go c.StartReader()for {select {case <- c.ExitBuffChan://得到退出消息,不再阻塞return}}}//停止连接,结束当前连接状态Mfunc (c *Connection) Stop() {//1. 如果当前链接已经关闭if c.isClosed == true {return}c.isClosed = true//TODO Connection Stop() 如果用户注册了该链接的关闭回调业务,那么在此刻应该显示调用// 关闭socket链接c.Conn.Close()//通知从缓冲队列读数据的业务,该链接已经关闭c.ExitBuffChan <- true//关闭该链接全部管道close(c.ExitBuffChan)}//从当前连接获取原始的socket TCPConnfunc (c *Connection) GetTCPConnection() *net.TCPConn {return c.Conn}//获取当前连接IDfunc (c *Connection) GetConnID() uint32{return c.ConnID}//获取远程客户端地址信息func (c *Connection) RemoteAddr() net.Addr {return c.Conn.RemoteAddr()}
C) 重新更正一下Server.go中 处理conn的连接业务
zinx/znet/server.go
package znetimport ("errors""fmt""net""time""zinx/ziface")//iServer 接口实现,定义一个Server服务类type Server struct {//服务器的名称Name string//tcp4 or otherIPVersion string//服务绑定的IP地址IP string//服务绑定的端口Port int}//============== 定义当前客户端链接的handle api ===========func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {//回显业务fmt.Println("[Conn Handle] CallBackToClient ... ")if _, err := conn.Write(data[:cnt]); err !=nil {fmt.Println("write back buf err ", err)return errors.New("CallBackToClient error")}return nil}//============== 实现 ziface.IServer 里的全部接口方法 ========//开启网络服务func (s *Server) Start() {fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port)//开启一个go去做服务端Linster业务go func() {//1 获取一个TCP的Addraddr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))if err != nil {fmt.Println("resolve tcp addr err: ", err)return}//2 监听服务器地址listenner, err:= net.ListenTCP(s.IPVersion, addr)if err != nil {fmt.Println("listen", s.IPVersion, "err", err)return}//已经监听成功fmt.Println("start Zinx server ", s.Name, " succ, now listenning...")//TODO server.go 应该有一个自动生成ID的方法var cid uint32cid = 0//3 启动server网络连接业务for {//3.1 阻塞等待客户端建立连接请求conn, err := listenner.AcceptTCP()if err != nil {fmt.Println("Accept err ", err)continue}//3.2 TODO Server.Start() 设置服务器最大连接控制,如果超过最大连接,那么则关闭此新的连接//3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的dealConn := NewConntion(conn, cid, CallBackToClient)cid ++//3.4 启动当前链接的处理业务go dealConn.Start()}}()}func (s *Server) Stop() {fmt.Println("[STOP] Zinx server , name " , s.Name)//TODO Server.Stop() 将其他需要清理的连接信息或者其他信息 也要一并停止或者清理}func (s *Server) Serve() {s.Start()//TODO Server.Serve() 是否在启动服务的时候 还要处理其他的事情呢 可以在这里添加//阻塞,否则主Go退出, listenner的go将会退出for {time.Sleep(10*time.Second)}}/*创建一个服务器句柄*/func NewServer (name string) ziface.IServer {s:= &Server {Name :name,IPVersion:"tcp4",IP:"0.0.0.0",Port:7777,}return s}
CallBackToClient是我们给当前客户端conn对象绑定的handle方法,当然目前是server端强制绑定的回显业务,我们之后会丰富框架,让这个用户可以让用户自定义指定handle。
在start()方法中,我们主要做了如下的修改:
//3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的dealConn := NewConntion(conn, cid, CallBackToClient)cid ++//3.4 启动当前链接的处理业务go dealConn.Start()
好了,现在我们已经将connection的连接和handle绑定了,下面我们在测试一下Zinx-V0.2的框架是否可以使用吧。
2.2 使用Zinx-V0.2完成应用程序
实际上,目前Zinx框架的对外接口并未改变,所以V0.1的测试依然有效。
Server.go
package mainimport ("zinx/znet")//Server 模块的测试函数func main() {//1 创建一个server 句柄 ss := znet.NewServer("[zinx V0.1]")//2 开启服务s.Serve()}
启动Server.go
go run Server.go
Client.go
package mainimport ("fmt""net""time")func main() {fmt.Println("Client Test ... start")//3秒之后发起测试请求,给服务端开启服务的机会time.Sleep(3 * time.Second)conn,err := net.Dial("tcp", "127.0.0.1:7777")if err != nil {fmt.Println("client start err, exit!")return}for {_, err := conn.Write([]byte("hahaha"))if err !=nil {fmt.Println("write error err ", err)return}buf :=make([]byte, 512)cnt, err := conn.Read(buf)if err != nil {fmt.Println("read buf error ")return}fmt.Printf(" server call back : %s, cnt = %d\n", buf, cnt)time.Sleep(1*time.Second)}}
启动Client.go进行测试
go run Client.go
现在我们已经简单初始了Zinx的雏形,但是目前离我们真正的框架还很远,接下来我们来改进zinx框架。
