现在我们要为Zinx框架增加链接个数的限定,如果超过一定量的客户端个数,Zinx为了保证后端的及时响应,而拒绝链接请求。

9.1 创建链接管理模块

这里面我们就需要对链接有一个管理的模块.

我们在zifaceznet分别建立iconnmanager.goconnmanager.go文件

zinx/ziface/iconmanager.go

  1. package ziface
  2. /*
  3. 连接管理抽象层
  4. */
  5. type IConnManager interface {
  6. Add(conn IConnection) //添加链接
  7. Remove(conn IConnection) //删除连接
  8. Get(connID uint32) (IConnection, error) //利用ConnID获取链接
  9. Len() int //获取当前连接
  10. ClearConn() //删除并停止所有链接
  11. }

这里定义了一些接口方法,添加链接、删除链接、根据ID获取链接、链接数量、和清除链接等。

zinx/znet/connmanager.go

  1. package znet
  2. import (
  3. "errors"
  4. "fmt"
  5. "sync"
  6. "zinx/ziface"
  7. )
  8. /*
  9. 连接管理模块
  10. */
  11. type ConnManager struct {
  12. connections map[uint32]ziface.IConnection //管理的连接信息
  13. connLock sync.RWMutex //读写连接的读写锁
  14. }
  15. /*
  16. 创建一个链接管理
  17. */
  18. func NewConnManager() *ConnManager {
  19. return &ConnManager{
  20. connections:make(map[uint32] ziface.IConnection),
  21. }
  22. }
  23. //添加链接
  24. func (connMgr *ConnManager) Add(conn ziface.IConnection) {
  25. //保护共享资源Map 加写锁
  26. connMgr.connLock.Lock()
  27. defer connMgr.connLock.Unlock()
  28. //将conn连接添加到ConnMananger中
  29. connMgr.connections[conn.GetConnID()] = conn
  30. fmt.Println("connection add to ConnManager successfully: conn num = ", connMgr.Len())
  31. }
  32. //删除连接
  33. func (connMgr *ConnManager) Remove(conn ziface.IConnection) {
  34. //保护共享资源Map 加写锁
  35. connMgr.connLock.Lock()
  36. defer connMgr.connLock.Unlock()
  37. //删除连接信息
  38. delete(connMgr.connections, conn.GetConnID())
  39. fmt.Println("connection Remove ConnID=",conn.GetConnID(), " successfully: conn num = ", connMgr.Len())
  40. }
  41. //利用ConnID获取链接
  42. func (connMgr *ConnManager) Get(connID uint32) (ziface.IConnection, error) {
  43. //保护共享资源Map 加读锁
  44. connMgr.connLock.RLock()
  45. defer connMgr.connLock.RUnlock()
  46. if conn, ok := connMgr.connections[connID]; ok {
  47. return conn, nil
  48. } else {
  49. return nil, errors.New("connection not found")
  50. }
  51. }
  52. //获取当前连接
  53. func (connMgr *ConnManager) Len() int {
  54. return len(connMgr.connections)
  55. }
  56. //清除并停止所有连接
  57. func (connMgr *ConnManager) ClearConn() {
  58. //保护共享资源Map 加写锁
  59. connMgr.connLock.Lock()
  60. defer connMgr.connLock.Unlock()
  61. //停止并删除全部的连接信息
  62. for connID, conn := range connMgr.connections {
  63. //停止
  64. conn.Stop()
  65. //删除
  66. delete(connMgr.connections,connID)
  67. }
  68. fmt.Println("Clear All Connections successfully: conn num = ", connMgr.Len())
  69. }

这里面ConnManager中,其中用一个map来承载全部的连接信息,key是连接ID,value则是连接本身。其中有一个读写锁connLock主要是针对map做多任务修改时的保护作用。

Remove()方法只是单纯的将conn从map中摘掉,而ClearConn()方法则会先停止链接业务,c.Stop(),然后再从map中摘除。

9.2 链接管理模块集成到Zinx中

A)ConnManager集成到Server中

现在需要将ConnManager添加到Server

zinx/znet/server.go

  1. //iServer 接口实现,定义一个Server服务类
  2. type Server struct {
  3. //服务器的名称
  4. Name string
  5. //tcp4 or other
  6. IPVersion string
  7. //服务绑定的IP地址
  8. IP string
  9. //服务绑定的端口
  10. Port int
  11. //当前Server的消息管理模块,用来绑定MsgId和对应的处理方法
  12. msgHandler ziface.IMsgHandle
  13. //当前Server的链接管理器
  14. ConnMgr ziface.IConnManager
  15. }
  16. /*
  17. 创建一个服务器句柄
  18. */
  19. func NewServer () ziface.IServer {
  20. utils.GlobalObject.Reload()
  21. s:= &Server {
  22. Name :utils.GlobalObject.Name,
  23. IPVersion:"tcp4",
  24. IP:utils.GlobalObject.Host,
  25. Port:utils.GlobalObject.TcpPort,
  26. msgHandler: NewMsgHandle(),
  27. ConnMgr:NewConnManager(), //创建ConnManager
  28. }
  29. return s
  30. }

那么,既然server具备了ConnManager成员,在获取的时候需要给抽象层提供一个获取ConnManager方法

zinx/ziface/iserver.go

  1. type IServer interface{
  2. //启动服务器方法
  3. Start()
  4. //停止服务器方法
  5. Stop()
  6. //开启业务服务方法
  7. Serve()
  8. //路由功能:给当前服务注册一个路由业务方法,供客户端链接处理使用
  9. AddRouter(msgId uint32, router IRouter)
  10. //得到链接管理
  11. GetConnMgr() IConnManager
  12. }

zinx/znet/server.go

  1. //得到链接管理
  2. func (s *Server) GetConnMgr() ziface.IConnManager {
  3. return s.ConnMgr
  4. }

因为我们现在在server中有链接的管理,有的时候conn也需要得到这个ConnMgr的使用权,那么我们需要将ServerConnection建立能够互相索引的关系,我们在Connection中,添加Server当前conn隶属的server句柄。

zinx/znet/connection.go

  1. type Connection struct {
  2. //当前Conn属于哪个Server
  3. TcpServer ziface.IServer //当前conn属于哪个server,在conn初始化的时候添加即可
  4. //当前连接的socket TCP套接字
  5. Conn *net.TCPConn
  6. //当前连接的ID 也可以称作为SessionID,ID全局唯一
  7. ConnID uint32
  8. //当前连接的关闭状态
  9. isClosed bool
  10. //消息管理MsgId和对应处理方法的消息管理模块
  11. MsgHandler ziface.IMsgHandle
  12. //告知该链接已经退出/停止的channel
  13. ExitBuffChan chan bool
  14. //无缓冲管道,用于读、写两个goroutine之间的消息通信
  15. msgChan chan []byte
  16. //有关冲管道,用于读、写两个goroutine之间的消息通信
  17. msgBuffChan chan []byte
  18. }

B) 链接的添加

那么我们什么选择将创建好的连接添加到ConnManager中呢,这里我们选择在初始化一个新链接的时候,加进来就好了

zinx/znet/connection.go

  1. //创建连接的方法
  2. func NewConntion(server ziface.IServer, conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection{
  3. //初始化Conn属性
  4. c := &Connection{
  5. TcpServer:server, //将隶属的server传递进来
  6. Conn: conn,
  7. ConnID: connID,
  8. isClosed: false,
  9. MsgHandler: msgHandler,
  10. ExitBuffChan: make(chan bool, 1),
  11. msgChan:make(chan []byte),
  12. msgBuffChan:make(chan []byte, utils.GlobalObject.MaxMsgChanLen),
  13. }
  14. //将新创建的Conn添加到链接管理中
  15. c.TcpServer.GetConnMgr().Add(c) //将当前新创建的连接添加到ConnManager中
  16. return c
  17. }

C) Server中添加链接数量的判断

在server的Start()方法中,在Accept与客户端链接建立成功后,可以直接对链接的个数做一个判断

zinx/znet/server.go

  1. //开启网络服务
  2. func (s *Server) Start() {
  3. fmt.Printf("[START] Server name: %s,listenner at IP: %s, Port %d is starting\n", s.Name, s.IP, s.Port)
  4. fmt.Printf("[Zinx] Version: %s, MaxConn: %d, MaxPacketSize: %d\n",
  5. utils.GlobalObject.Version,
  6. utils.GlobalObject.MaxConn,
  7. utils.GlobalObject.MaxPacketSize)
  8. //开启一个go去做服务端Linster业务
  9. go func() {
  10. // ....
  11. //3 启动server网络连接业务
  12. for {
  13. //3.1 阻塞等待客户端建立连接请求
  14. conn, err := listenner.AcceptTCP()
  15. if err != nil {
  16. fmt.Println("Accept err ", err)
  17. continue
  18. }
  19. //=============
  20. //3.2 设置服务器最大连接控制,如果超过最大连接,那么则关闭此新的连接
  21. if s.ConnMgr.Len() >= utils.GlobalObject.MaxConn {
  22. conn.Close()
  23. continue
  24. }
  25. //=============
  26. //3.3 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的
  27. dealConn := NewConntion(s, conn, cid, s.msgHandler)
  28. cid ++
  29. //3.4 启动当前链接的处理业务
  30. go dealConn.Start()
  31. }
  32. }()
  33. }

当然,我们应该在配置文件zinx.json或者在GlobalObject全局配置中,定义好我们期望的连接的最大数目限制MaxConn

D) 连接的删除

我们应该在连接停止的时候,将该连接从ConnManager中删除,所以在connectionStop()方法中添加。

zinx/znet/connecion.go

  1. func (c *Connection) Stop() {
  2. fmt.Println("Conn Stop()...ConnID = ", c.ConnID)
  3. //如果当前链接已经关闭
  4. if c.isClosed == true {
  5. return
  6. }
  7. c.isClosed = true
  8. // 关闭socket链接
  9. c.Conn.Close()
  10. //关闭Writer Goroutine
  11. c.ExitBuffChan <- true
  12. //将链接从连接管理器中删除
  13. c.TcpServer.GetConnMgr().Remove(c) //删除conn从ConnManager中
  14. //关闭该链接全部管道
  15. close(c.ExitBuffChan)
  16. close(c.msgBuffChan)
  17. }

当然,我们也应该在server停止的时候,将全部的连接清空

zinx/znet/server.go

  1. func (s *Server) Stop() {
  2. fmt.Println("[STOP] Zinx server , name " , s.Name)
  3. //将其他需要清理的连接信息或者其他信息 也要一并停止或者清理
  4. s.ConnMgr.ClearConn()
  5. }

现在我们已经将连接管理成功的集成到了Zinx之中了。

9.3 链接的带缓冲的发包方法

我们之前给Connection提供了一个发消息的方法SendMsg(),这个是将数据发送到一个无缓冲的channel中msgChan。但是如果客户端链接比较多的话,如果对方处理不及时,可能会出现短暂的阻塞现象,我们可以做一个提供一定缓冲的发消息方法,做一些非阻塞的发送体验。

zinx/ziface/iconnection.go

  1. //定义连接接口
  2. type IConnection interface {
  3. //启动连接,让当前连接开始工作
  4. Start()
  5. //停止连接,结束当前连接状态M
  6. Stop()
  7. //从当前连接获取原始的socket TCPConn
  8. GetTCPConnection() *net.TCPConn
  9. //获取当前连接ID
  10. GetConnID() uint32
  11. //获取远程客户端地址信息
  12. RemoteAddr() net.Addr
  13. //直接将Message数据发送数据给远程的TCP客户端(无缓冲)
  14. SendMsg(msgId uint32, data []byte) error
  15. //直接将Message数据发送给远程的TCP客户端(有缓冲)
  16. SendBuffMsg(msgId uint32, data []byte) error //添加带缓冲发送消息接口
  17. }

zinx/znet/connection.go

  1. type Connection struct {
  2. //当前Conn属于哪个Server
  3. TcpServer ziface.IServer
  4. //当前连接的socket TCP套接字
  5. Conn *net.TCPConn
  6. //当前连接的ID 也可以称作为SessionID,ID全局唯一
  7. ConnID uint32
  8. //当前连接的关闭状态
  9. isClosed bool
  10. //消息管理MsgId和对应处理方法的消息管理模块
  11. MsgHandler ziface.IMsgHandle
  12. //告知该链接已经退出/停止的channel
  13. ExitBuffChan chan bool
  14. //无缓冲管道,用于读、写两个goroutine之间的消息通信
  15. msgChan chan []byte
  16. //有关冲管道,用于读、写两个goroutine之间的消息通信
  17. msgBuffChan chan []byte //定义channel成员
  18. }
  19. //创建连接的方法
  20. func NewConntion(server ziface.IServer, conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection{
  21. //初始化Conn属性
  22. c := &Connection{
  23. TcpServer:server,
  24. Conn: conn,
  25. ConnID: connID,
  26. isClosed: false,
  27. MsgHandler: msgHandler,
  28. ExitBuffChan: make(chan bool, 1),
  29. msgChan:make(chan []byte),
  30. msgBuffChan:make(chan []byte, utils.GlobalObject.MaxMsgChanLen), //不要忘记初始化
  31. }
  32. //将新创建的Conn添加到链接管理中
  33. c.TcpServer.GetConnMgr().Add(c)
  34. return c
  35. }

然后将SendBuffMsg()方法实现一下:

  1. func (c *Connection) SendBuffMsg(msgId uint32, data []byte) error {
  2. if c.isClosed == true {
  3. return errors.New("Connection closed when send buff msg")
  4. }
  5. //将data封包,并且发送
  6. dp := NewDataPack()
  7. msg, err := dp.Pack(NewMsgPackage(msgId, data))
  8. if err != nil {
  9. fmt.Println("Pack error msg id = ", msgId)
  10. return errors.New("Pack error msg ")
  11. }
  12. //写回客户端
  13. c.msgBuffChan <- msg
  14. return nil
  15. }

我们在Writer中也要有对msgBuffChan的数据监控:

  1. /*
  2. 写消息Goroutine, 用户将数据发送给客户端
  3. */
  4. func (c *Connection) StartWriter() {
  5. fmt.Println("[Writer Goroutine is running]")
  6. defer fmt.Println(c.RemoteAddr().String(), "[conn Writer exit!]")
  7. for {
  8. select {
  9. case data := <-c.msgChan:
  10. //有数据要写给客户端
  11. if _, err := c.Conn.Write(data); err != nil {
  12. fmt.Println("Send Data error:, ", err, " Conn Writer exit")
  13. return
  14. }
  15. //针对有缓冲channel需要些的数据处理
  16. case data, ok:= <-c.msgBuffChan:
  17. if ok {
  18. //有数据要写给客户端
  19. if _, err := c.Conn.Write(data); err != nil {
  20. fmt.Println("Send Buff Data error:, ", err, " Conn Writer exit")
  21. return
  22. }
  23. } else {
  24. break
  25. fmt.Println("msgBuffChan is Closed")
  26. }
  27. case <-c.ExitBuffChan:
  28. return
  29. }
  30. }
  31. }

9.4 注册链接启动/停止自定义Hook方法功能

有的时候,在创建链接的时候,希望在创建链接之后、和断开链接之前,执行一些用户自定义的业务。那么我们就需要给Zinx增添两个链接创建后和断开前时机的回调函数,一般也称作Hook(钩子)函数。

我们可以通过Server来注册conn的hook方法

zinx/ziface/iserver.go

  1. type IServer interface{
  2. //启动服务器方法
  3. Start()
  4. //停止服务器方法
  5. Stop()
  6. //开启业务服务方法
  7. Serve()
  8. //路由功能:给当前服务注册一个路由业务方法,供客户端链接处理使用
  9. AddRouter(msgId uint32, router IRouter)
  10. //得到链接管理
  11. GetConnMgr() IConnManager
  12. //设置该Server的连接创建时Hook函数
  13. SetOnConnStart(func (IConnection))
  14. //设置该Server的连接断开时的Hook函数
  15. SetOnConnStop(func (IConnection))
  16. //调用连接OnConnStart Hook函数
  17. CallOnConnStart(conn IConnection)
  18. //调用连接OnConnStop Hook函数
  19. CallOnConnStop(conn IConnection)
  20. }

zinx/znet/server.go

  1. //iServer 接口实现,定义一个Server服务类
  2. type Server struct {
  3. //服务器的名称
  4. Name string
  5. //tcp4 or other
  6. IPVersion string
  7. //服务绑定的IP地址
  8. IP string
  9. //服务绑定的端口
  10. Port int
  11. //当前Server的消息管理模块,用来绑定MsgId和对应的处理方法
  12. msgHandler ziface.IMsgHandle
  13. //当前Server的链接管理器
  14. ConnMgr ziface.IConnManager
  15. // =======================
  16. //新增两个hook函数原型
  17. //该Server的连接创建时Hook函数
  18. OnConnStart func(conn ziface.IConnection)
  19. //该Server的连接断开时的Hook函数
  20. OnConnStop func(conn ziface.IConnection)
  21. // =======================
  22. }

实现添加hook函数的接口和调用hook函数的接口

  1. //设置该Server的连接创建时Hook函数
  2. func (s *Server) SetOnConnStart(hookFunc func (ziface.IConnection)) {
  3. s.OnConnStart = hookFunc
  4. }
  5. //设置该Server的连接断开时的Hook函数
  6. func (s *Server) SetOnConnStop(hookFunc func (ziface.IConnection)) {
  7. s.OnConnStop = hookFunc
  8. }
  9. //调用连接OnConnStart Hook函数
  10. func (s *Server) CallOnConnStart(conn ziface.IConnection) {
  11. if s.OnConnStart != nil {
  12. fmt.Println("---> CallOnConnStart....")
  13. s.OnConnStart(conn)
  14. }
  15. }
  16. //调用连接OnConnStop Hook函数
  17. func (s *Server) CallOnConnStop(conn ziface.IConnection) {
  18. if s.OnConnStop != nil {
  19. fmt.Println("---> CallOnConnStop....")
  20. s.OnConnStop(conn)
  21. }
  22. }

那么接下来,需要选定两个Hook方法的调用位置。

一个是创建链接之后:

zinx/znet/connection.go

  1. //启动连接,让当前连接开始工作
  2. func (c *Connection) Start() {
  3. //1 开启用户从客户端读取数据流程的Goroutine
  4. go c.StartReader()
  5. //2 开启用于写回客户端数据流程的Goroutine
  6. go c.StartWriter()
  7. //==================
  8. //按照用户传递进来的创建连接时需要处理的业务,执行钩子方法
  9. c.TcpServer.CallOnConnStart(c)
  10. //==================
  11. }

一个是停止链接之前:

zinx/znet/connection.go

  1. //停止连接,结束当前连接状态M
  2. func (c *Connection) Stop() {
  3. fmt.Println("Conn Stop()...ConnID = ", c.ConnID)
  4. //如果当前链接已经关闭
  5. if c.isClosed == true {
  6. return
  7. }
  8. c.isClosed = true
  9. //==================
  10. //如果用户注册了该链接的关闭回调业务,那么在此刻应该显示调用
  11. c.TcpServer.CallOnConnStop(c)
  12. //==================
  13. // 关闭socket链接
  14. c.Conn.Close()
  15. //关闭Writer
  16. c.ExitBuffChan <- true
  17. //将链接从连接管理器中删除
  18. c.TcpServer.GetConnMgr().Remove(c)
  19. //关闭该链接全部管道
  20. close(c.ExitBuffChan)
  21. close(c.msgBuffChan)
  22. }

9.5 使用Zinx-V0.9完成应用程序

好了,现在我们基本上已经将全部的连接管理的功能集成到Zinx中了,接下来就需要测试一下链接管理模块是否可以使用了。

写一个服务端:

Server.go

  1. package main
  2. import (
  3. "fmt"
  4. "zinx/ziface"
  5. "zinx/znet"
  6. )
  7. //ping test 自定义路由
  8. type PingRouter struct {
  9. znet.BaseRouter
  10. }
  11. //Ping Handle
  12. func (this *PingRouter) Handle(request ziface.IRequest) {
  13. fmt.Println("Call PingRouter Handle")
  14. //先读取客户端的数据,再回写ping...ping...ping
  15. fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
  16. err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))
  17. if err != nil {
  18. fmt.Println(err)
  19. }
  20. }
  21. type HelloZinxRouter struct {
  22. znet.BaseRouter
  23. }
  24. //HelloZinxRouter Handle
  25. func (this *HelloZinxRouter) Handle(request ziface.IRequest) {
  26. fmt.Println("Call HelloZinxRouter Handle")
  27. //先读取客户端的数据,再回写ping...ping...ping
  28. fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
  29. err := request.GetConnection().SendBuffMsg(1, []byte("Hello Zinx Router V0.8"))
  30. if err != nil {
  31. fmt.Println(err)
  32. }
  33. }
  34. //创建连接的时候执行
  35. func DoConnectionBegin(conn ziface.IConnection) {
  36. fmt.Println("DoConnecionBegin is Called ... ")
  37. err := conn.SendMsg(2, []byte("DoConnection BEGIN..."))
  38. if err != nil {
  39. fmt.Println(err)
  40. }
  41. }
  42. //连接断开的时候执行
  43. func DoConnectionLost(conn ziface.IConnection) {
  44. fmt.Println("DoConneciotnLost is Called ... ")
  45. }
  46. func main() {
  47. //创建一个server句柄
  48. s := znet.NewServer()
  49. //注册链接hook回调函数
  50. s.SetOnConnStart(DoConnectionBegin)
  51. s.SetOnConnStop(DoConnectionLost)
  52. //配置路由
  53. s.AddRouter(0, &PingRouter{})
  54. s.AddRouter(1, &HelloZinxRouter{})
  55. //开启服务
  56. s.Serve()
  57. }

我们这里注册了两个Hook函数一个是链接初始化之后DoConnectionBegin()和链接停止之前DoConnectionLost()

DoConnectionBegin()会发给客户端一个消息2的文本,并且在服务端打印一个调试信息”DoConnecionBegin is Called … “

DoConnectionLost()在服务端打印一个调试信息”DoConneciotnLost is Called … “

客户端:

Client.go

  1. package main
  2. import (
  3. "fmt"
  4. "io"
  5. "net"
  6. "time"
  7. "zinx/znet"
  8. )
  9. /*
  10. 模拟客户端
  11. */
  12. func main() {
  13. fmt.Println("Client Test ... start")
  14. //3秒之后发起测试请求,给服务端开启服务的机会
  15. time.Sleep(3 * time.Second)
  16. conn,err := net.Dial("tcp", "127.0.0.1:7777")
  17. if err != nil {
  18. fmt.Println("client start err, exit!")
  19. return
  20. }
  21. for {
  22. //发封包message消息
  23. dp := znet.NewDataPack()
  24. msg, _ := dp.Pack(znet.NewMsgPackage(0,[]byte("Zinx V0.8 Client0 Test Message")))
  25. _, err := conn.Write(msg)
  26. if err !=nil {
  27. fmt.Println("write error err ", err)
  28. return
  29. }
  30. //先读出流中的head部分
  31. headData := make([]byte, dp.GetHeadLen())
  32. _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止
  33. if err != nil {
  34. fmt.Println("read head error")
  35. break
  36. }
  37. //将headData字节流 拆包到msg中
  38. msgHead, err := dp.Unpack(headData)
  39. if err != nil {
  40. fmt.Println("server unpack err:", err)
  41. return
  42. }
  43. if msgHead.GetDataLen() > 0 {
  44. //msg 是有data数据的,需要再次读取data数据
  45. msg := msgHead.(*znet.Message)
  46. msg.Data = make([]byte, msg.GetDataLen())
  47. //根据dataLen从io中读取字节流
  48. _, err := io.ReadFull(conn, msg.Data)
  49. if err != nil {
  50. fmt.Println("server unpack data err:", err)
  51. return
  52. }
  53. fmt.Println("==> Recv Msg: ID=", msg.Id, ", len=", msg.DataLen, ", data=", string(msg.Data))
  54. }
  55. time.Sleep(1*time.Second)
  56. }
  57. }

代码不变。

启动服务端

  1. $go run Server.go

启动客户端

  1. $go run Client.go

服务端结果:

  1. $ go run Server.go
  2. Add api msgId = 0
  3. Add api msgId = 1
  4. [START] Server name: zinx v-0.8 demoApp,listenner at IP: 127.0.0.1, Port 7777 is starting
  5. [Zinx] Version: V0.4, MaxConn: 3, MaxPacketSize: 4096
  6. start Zinx server zinx v-0.8 demoApp succ, now listenning...
  7. Worker ID = 9 is started.
  8. Worker ID = 5 is started.
  9. Worker ID = 6 is started.
  10. Worker ID = 7 is started.
  11. Worker ID = 8 is started.
  12. Worker ID = 1 is started.
  13. Worker ID = 0 is started.
  14. Worker ID = 2 is started.
  15. Worker ID = 3 is started.
  16. Worker ID = 4 is started.
  17. connection add to ConnManager successfully: conn num = 1
  18. ---> CallOnConnStart....
  19. DoConnecionBegin is Called ...
  20. [Writer Goroutine is running]
  21. [Reader Goroutine is running]
  22. Add ConnID= 0 request msgID= 0 to workerID= 0
  23. Call PingRouter Handle
  24. recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
  25. Add ConnID= 0 request msgID= 0 to workerID= 0
  26. Call PingRouter Handle
  27. recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
  28. Add ConnID= 0 request msgID= 0 to workerID= 0
  29. Call PingRouter Handle
  30. recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
  31. Add ConnID= 0 request msgID= 0 to workerID= 0
  32. Call PingRouter Handle
  33. recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
  34. Add ConnID= 0 request msgID= 0 to workerID= 0
  35. Call PingRouter Handle
  36. recv from client : msgId= 0 , data= Zinx V0.8 Client0 Test Message
  37. read msg head error read tcp4 127.0.0.1:7777->127.0.0.1:49510: read: connection reset by peer
  38. Conn Stop()...ConnID = 0
  39. ---> CallOnConnStop....
  40. DoConneciotnLost is Called ...
  41. connection Remove ConnID= 0 successfully: conn num = 0
  42. 127.0.0.1:49510 [conn Reader exit!]
  43. 127.0.0.1:49510 [conn Writer exit!]

客户端结果:

  1. $ go run Client0.go
  2. Client Test ... start
  3. ==> Recv Msg: ID= 2 , len= 21 , data= DoConnection BEGIN...
  4. ==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
  5. ==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
  6. ==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
  7. ==> Recv Msg: ID= 0 , len= 18 , data= ping...ping...ping
  8. ^Csignal: interrupt

客户端创建成功,回调Hook已经执行,并且Conn被添加到ConnManager 中, conn num = 1,

当我们手动CTRL+C 关闭客户端的时候, 服务器ConnManager已经成功将Conn摘掉,conn num = 0.

同时服务端也打印出 conn停止之后的回调信息。