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

  1. package ziface
  2. import "net"
  3. //定义连接接口
  4. type IConnection interface {
  5. //启动连接,让当前连接开始工作
  6. Start()
  7. //停止连接,结束当前连接状态M
  8. Stop()
  9. //从当前连接获取原始的socket TCPConn
  10. GetTCPConnection() *net.TCPConn
  11. //获取当前连接ID
  12. GetConnID() uint32
  13. //获取远程客户端地址信息
  14. RemoteAddr() net.Addr
  15. }
  16. //定义一个统一处理链接业务的接口
  17. type HandFunc func(*net.TCPConn, []byte, int) error

该接口的一些基础方法,代码注释已经介绍的很清楚,这里先简单说明一个HandFunc这个函数类型,这个是所有conn链接在处理业务的函数接口,第一参数是socket原生链接,第二个参数是客户端请求的数据,第三个参数是客户端请求的数据长度。这样,如果我们想要指定一个conn的处理业务,只要定义一个HandFunc类型的函数,然后和该链接绑定就可以了。

B) znet 创建iconnection.go

zinx/znet/connection.go

  1. package znet
  2. import (
  3. "fmt"
  4. "net"
  5. "zinx/ziface"
  6. )
  7. type Connection struct {
  8. //当前连接的socket TCP套接字
  9. Conn *net.TCPConn
  10. //当前连接的ID 也可以称作为SessionID,ID全局唯一
  11. ConnID uint32
  12. //当前连接的关闭状态
  13. isClosed bool
  14. //该连接的处理方法api
  15. handleAPI ziface.HandFunc
  16. //告知该链接已经退出/停止的channel
  17. ExitBuffChan chan bool
  18. }
  19. //创建连接的方法
  20. func NewConntion(conn *net.TCPConn, connID uint32, callback_api ziface.HandFunc) *Connection{
  21. c := &Connection{
  22. Conn: conn,
  23. ConnID: connID,
  24. isClosed: false,
  25. handleAPI: callback_api,
  26. ExitBuffChan: make(chan bool, 1),
  27. }
  28. return c
  29. }
  30. /* 处理conn读数据的Goroutine */
  31. func (c *Connection) StartReader() {
  32. fmt.Println("Reader Goroutine is running")
  33. defer fmt.Println(c.RemoteAddr().String(), " conn reader exit!")
  34. defer c.Stop()
  35. for {
  36. //读取我们最大的数据到buf中
  37. buf := make([]byte, 512)
  38. cnt, err := c.Conn.Read(buf)
  39. if err != nil {
  40. fmt.Println("recv buf err ", err)
  41. c.ExitBuffChan <- true
  42. continue
  43. }
  44. //调用当前链接业务(这里执行的是当前conn的绑定的handle方法)
  45. if err := c.handleAPI(c.Conn, buf, cnt); err !=nil {
  46. fmt.Println("connID ", c.ConnID, " handle is error")
  47. c.ExitBuffChan <- true
  48. return
  49. }
  50. }
  51. }
  52. //启动连接,让当前连接开始工作
  53. func (c *Connection) Start() {
  54. //开启处理该链接读取到客户端数据之后的请求业务
  55. go c.StartReader()
  56. for {
  57. select {
  58. case <- c.ExitBuffChan:
  59. //得到退出消息,不再阻塞
  60. return
  61. }
  62. }
  63. }
  64. //停止连接,结束当前连接状态M
  65. func (c *Connection) Stop() {
  66. //1. 如果当前链接已经关闭
  67. if c.isClosed == true {
  68. return
  69. }
  70. c.isClosed = true
  71. //TODO Connection Stop() 如果用户注册了该链接的关闭回调业务,那么在此刻应该显示调用
  72. // 关闭socket链接
  73. c.Conn.Close()
  74. //通知从缓冲队列读数据的业务,该链接已经关闭
  75. c.ExitBuffChan <- true
  76. //关闭该链接全部管道
  77. close(c.ExitBuffChan)
  78. }
  79. //从当前连接获取原始的socket TCPConn
  80. func (c *Connection) GetTCPConnection() *net.TCPConn {
  81. return c.Conn
  82. }
  83. //获取当前连接ID
  84. func (c *Connection) GetConnID() uint32{
  85. return c.ConnID
  86. }
  87. //获取远程客户端地址信息
  88. func (c *Connection) RemoteAddr() net.Addr {
  89. return c.Conn.RemoteAddr()
  90. }
C) 重新更正一下Server.go中 处理conn的连接业务

zinx/znet/server.go

  1. package znet
  2. import (
  3. "errors"
  4. "fmt"
  5. "net"
  6. "time"
  7. "zinx/ziface"
  8. )
  9. //iServer 接口实现,定义一个Server服务类
  10. type Server struct {
  11. //服务器的名称
  12. Name string
  13. //tcp4 or other
  14. IPVersion string
  15. //服务绑定的IP地址
  16. IP string
  17. //服务绑定的端口
  18. Port int
  19. }
  20. //============== 定义当前客户端链接的handle api ===========
  21. func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {
  22. //回显业务
  23. fmt.Println("[Conn Handle] CallBackToClient ... ")
  24. if _, err := conn.Write(data[:cnt]); err !=nil {
  25. fmt.Println("write back buf err ", err)
  26. return errors.New("CallBackToClient error")
  27. }
  28. return nil
  29. }
  30. //============== 实现 ziface.IServer 里的全部接口方法 ========
  31. //开启网络服务
  32. func (s *Server) Start() {
  33. fmt.Printf("[START] Server listenner at IP: %s, Port %d, is starting\n", s.IP, s.Port)
  34. //开启一个go去做服务端Linster业务
  35. go func() {
  36. //1 获取一个TCP的Addr
  37. addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))
  38. if err != nil {
  39. fmt.Println("resolve tcp addr err: ", err)
  40. return
  41. }
  42. //2 监听服务器地址
  43. listenner, err:= net.ListenTCP(s.IPVersion, addr)
  44. if err != nil {
  45. fmt.Println("listen", s.IPVersion, "err", err)
  46. return
  47. }
  48. //已经监听成功
  49. fmt.Println("start Zinx server ", s.Name, " succ, now listenning...")
  50. //TODO server.go 应该有一个自动生成ID的方法
  51. var cid uint32
  52. cid = 0
  53. //3 启动server网络连接业务
  54. for {
  55. //3.1 阻塞等待客户端建立连接请求
  56. conn, err := listenner.AcceptTCP()
  57. if err != nil {
  58. fmt.Println("Accept err ", err)
  59. continue
  60. }
  61. //3.2 TODO Server.Start() 设置服务器最大连接控制,如果超过最大连接,那么则关闭此新的连接
  62. //3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
  63. dealConn := NewConntion(conn, cid, CallBackToClient)
  64. cid ++
  65. //3.4 启动当前链接的处理业务
  66. go dealConn.Start()
  67. }
  68. }()
  69. }
  70. func (s *Server) Stop() {
  71. fmt.Println("[STOP] Zinx server , name " , s.Name)
  72. //TODO Server.Stop() 将其他需要清理的连接信息或者其他信息 也要一并停止或者清理
  73. }
  74. func (s *Server) Serve() {
  75. s.Start()
  76. //TODO Server.Serve() 是否在启动服务的时候 还要处理其他的事情呢 可以在这里添加
  77. //阻塞,否则主Go退出, listenner的go将会退出
  78. for {
  79. time.Sleep(10*time.Second)
  80. }
  81. }
  82. /*
  83. 创建一个服务器句柄
  84. */
  85. func NewServer (name string) ziface.IServer {
  86. s:= &Server {
  87. Name :name,
  88. IPVersion:"tcp4",
  89. IP:"0.0.0.0",
  90. Port:7777,
  91. }
  92. return s
  93. }

CallBackToClient是我们给当前客户端conn对象绑定的handle方法,当然目前是server端强制绑定的回显业务,我们之后会丰富框架,让这个用户可以让用户自定义指定handle。

start()方法中,我们主要做了如下的修改:

  1. //3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
  2. dealConn := NewConntion(conn, cid, CallBackToClient)
  3. cid ++
  4. //3.4 启动当前链接的处理业务
  5. go dealConn.Start()

好了,现在我们已经将connection的连接和handle绑定了,下面我们在测试一下Zinx-V0.2的框架是否可以使用吧。

2.2 使用Zinx-V0.2完成应用程序

实际上,目前Zinx框架的对外接口并未改变,所以V0.1的测试依然有效。

Server.go

  1. package main
  2. import (
  3. "zinx/znet"
  4. )
  5. //Server 模块的测试函数
  6. func main() {
  7. //1 创建一个server 句柄 s
  8. s := znet.NewServer("[zinx V0.1]")
  9. //2 开启服务
  10. s.Serve()
  11. }

启动Server.go

  1. go run Server.go

Client.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. "time"
  6. )
  7. func main() {
  8. fmt.Println("Client Test ... start")
  9. //3秒之后发起测试请求,给服务端开启服务的机会
  10. time.Sleep(3 * time.Second)
  11. conn,err := net.Dial("tcp", "127.0.0.1:7777")
  12. if err != nil {
  13. fmt.Println("client start err, exit!")
  14. return
  15. }
  16. for {
  17. _, err := conn.Write([]byte("hahaha"))
  18. if err !=nil {
  19. fmt.Println("write error err ", err)
  20. return
  21. }
  22. buf :=make([]byte, 512)
  23. cnt, err := conn.Read(buf)
  24. if err != nil {
  25. fmt.Println("read buf error ")
  26. return
  27. }
  28. fmt.Printf(" server call back : %s, cnt = %d\n", buf, cnt)
  29. time.Sleep(1*time.Second)
  30. }
  31. }

启动Client.go进行测试

  1. go run Client.go

现在我们已经简单初始了Zinx的雏形,但是目前离我们真正的框架还很远,接下来我们来改进zinx框架。