好了,接下来我们就要对Zinx做一个小小的改变,就是与客户端进修数据交互的Gouroutine由一个变成两个,一个专门负责从客户端读取数据,一个专门负责向客户端写数据。这么设计有什么好处,当然是目的就是高内聚,模块的功能单一,对于我们今后扩展功能更加方便。
我们希望Zinx在升级到V0.7版本的时候,架构是下面这样的:

Zinx-V0.7架构模型

  1. Server依然是处理客户端的响应,主要关键的几个方法是ListenAccept等。当建立与客户端的套接字后,那么就会开启两个Goroutine分别处理读数据业务和写数据业务,读写数据之间的消息通过一个Channel传递。

7.1 Zinx-V0.7代码实现

我们的代码改动并不是很大。

A) 添加读写模块交互数据的管道

zinx/znet/connection.go

  1. type Connection struct {
  2. //当前连接的socket TCP套接字
  3. Conn *net.TCPConn
  4. //当前连接的ID 也可以称作为SessionID,ID全局唯一
  5. ConnID uint32
  6. //当前连接的关闭状态
  7. isClosed bool
  8. //消息管理MsgId和对应处理方法的消息管理模块
  9. MsgHandler ziface.IMsgHandle
  10. //告知该链接已经退出/停止的channel
  11. ExitBuffChan chan bool
  12. //无缓冲管道,用于读、写两个goroutine之间的消息通信
  13. msgChan chan []byte
  14. }
  15. //创建连接的方法
  16. func NewConntion(conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection{
  17. c := &Connection{
  18. Conn: conn,
  19. ConnID: connID,
  20. isClosed: false,
  21. MsgHandler: msgHandler,
  22. ExitBuffChan: make(chan bool, 1),
  23. msgChan:make(chan []byte), //msgChan初始化
  24. }
  25. return c
  26. }

我们给Connection新增一个管道成员msgChan,作用是用于读写两个go的通信。

B) 创建Writer Goroutine

zinx/znet/connection.go

  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. case <- c.ExitBuffChan:
  16. //conn已经关闭
  17. return
  18. }
  19. }
  20. }

C) Reader讲发送客户端的数据改为发送至Channel

修改Reader调用的SendMsg()方法

zinx/znet/connection.go

  1. //直接将Message数据发送数据给远程的TCP客户端
  2. func (c *Connection) SendMsg(msgId uint32, data []byte) error {
  3. if c.isClosed == true {
  4. return errors.New("Connection closed when send msg")
  5. }
  6. //将data封包,并且发送
  7. dp := NewDataPack()
  8. msg, err := dp.Pack(NewMsgPackage(msgId, data))
  9. if err != nil {
  10. fmt.Println("Pack error msg id = ", msgId)
  11. return errors.New("Pack error msg ")
  12. }
  13. //写回客户端
  14. c.msgChan <- msg //将之前直接回写给conn.Write的方法 改为 发送给Channel 供Writer读取
  15. return nil
  16. }

D) 启动Reader和Writer

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. for {
  8. select {
  9. case <- c.ExitBuffChan:
  10. //得到退出消息,不再阻塞
  11. return
  12. }
  13. }
  14. }

7.2 使用Zinx-V0.7完成应用程序

测试代码和V0.6的代码一样。
现在我们已经将读写模块分离了,那么接下来我们就可以再升级添加任务队列机制了。