7.构建Zinx的读写分离模型
#go #zinx

<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>


在本章中,我们将对ZINX进行较小的修改,以将其与客户端分开的新版本将其分开为两个单独的goroutines。一个goroutine将负责从客户端读取数据,而另一个将负责向客户写数据。该设计提供了诸如高凝聚力和模块化功能之类的好处,从而使将来扩展系统的功能变得更加容易。更新的读写架构如图7.1所示。

Image description

图18.1:读取式分离模型

服务器继续处理客户端响应,并包括以前的关键方法,例如Listen()Accept()。与客户端建立了插座连接后,将启动两个Goroutines来分别处理和写入操作。读取过程之间的消息通过频道传递。

7.1 ZINX-V0.7代码的实现

读写分离模型的代码修改如下。变化很小,涉及四个改进领域。

7.1.1添加读写模块的通信通道

首先,我们需要在两个goroutines之间添加一个通信渠道,以进行阅读和写作。该通道将用于在两个goroutines之间传输信号和状态数据。我们在Connection结构中定义了此频道,因为阅读和写作Goroutines都将在已经建立的当前连接上运行。更新的Connection数据结构如下:

// zinx/znet/connection.go

type Connection struct {
    // Current TCP socket connection
    Conn *net.TCPConn
    // Unique ID for the connection, also known as SessionID
    ConnID uint32
    // Connection close status
    isClosed bool
    // Message handler module that manages message IDs and corresponding processing methods
    MsgHandler ziface.IMsgHandle
    // Channel to signal that the connection has exited/stopped
    ExitBuffChan chan bool
    // Unbuffered channel for message communication between the read and write Goroutines
    msgChan chan []byte
}

我们将新的频道成员msgChan添加到Connection。它的目的是促进读写和写goroutines之间的沟通。此外,Connection的构造方法需要初始化msgChan属性。修改的代码如下:

// Create a connection method
func NewConnection(conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection {
    c := &Connection{
        Conn:          conn,
        ConnID:        connID,
        isClosed:      false,
        MsgHandler:    msgHandler,
        ExitBuffChan:  make(chan bool, 1),
        msgChan:       make(chan []byte), // Initialize msgChan
    }

    return c
}

7.1.2创建作者Goroutine:

我们需要写一个goroutine,以向客户发送消息。核心代码涉及调用conn.Write(),逻辑等待读取Goroutine发送的消息。代码如下:

// zinx/znet/connection.go

/*
    Writer Goroutine: Sends data to the client
*/
func (c *Connection) StartWriter() {
    fmt.Println("[Writer Goroutine is running]")
    defer fmt.Println(c.RemoteAddr().String(), "[conn Writer exit!]")

    for {
        select {
        case data := <-c.msgChan:
            // Data to be written to the client
            if _, err := c.Conn.Write(data); err != nil {
                fmt.Println("Send Data error:", err, "Conn Writer exit")
                return
            }
        case <-c.ExitBuffChan:
            // Connection has been closed
            return
        }
    }
}

创建StartWriter() Goroutine的优点是它将阅读和写作业务逻辑分开,使职责清晰,并允许独立优化阅读或写入过程而不相互干扰。

7.1.3修改读者以将数据发送到频道:

我们需要修改读者调用的sendmsg()方法,以将消息发送给作者,而不是直接写回客户。相关代码如下:

// zinx/znet/connection.go

// Send Message: Send message data directly to the remote TCP client
func (c *Connection) SendMsg(msgID uint32, data []byte) error {
    if c.isClosed == true {
        return errors.New("Connection closed when sending message")
    }

    // Package the data and send it
    dp := NewDataPack()
    msg, err := dp.Pack(NewMsgPackage(msgID, data))
    if err != nil {
        fmt.Println("Pack error, msgID =", msgID)
        return errors.New("Pack error message")
    }

    // Write back to the client
    // Change the previous direct write using conn.Write to sending the message to the Channel for the Writer to read
    c.msgChan <- msg

    return nil
}

7.1.4启动读者和作家:

启动连接的逻辑变得更加清晰。我们只需要单独启动读者和作家Goroutines即可处理连接的读写操作。启动连接的代码如下:

// zinx/znet/connection.go

// Start the connection and make it work
func (c *Connection) Start() {
    // 1. Start a Goroutine for reading data from the client
    go c.StartReader()
    // 2. Start a Goroutine for writing data back to the client
    go c.StartWriter()

    for {
        select {
        case <-c.ExitBuffChan:
            // Received exit message, no longer block
            return
        }
    }
}

7.2摘要

测试代码与v0.6中的相同,因此在这里不会重申。我们已经成功分离了读写模块。读写分离设计的重要性在于,它为将来改进提供了明确的方向,可以对读取过程进行目标优化。下一步将是通过添加任务队列机制来基于此读写分离进行进一步升级。


<1.Building Basic Services with Zinx Framework>
<2. Zinx-V0.2 Simple Connection Encapsulation and Binding with Business>
<3.Design and Implementation of the Zinx Framework's Routing Module>
<4.Zinx Global Configuration>
<5.Zinx Message Encapsulation Module Design and Implementation>
<6.Design and Implementation of Zinx Multi-Router Mode>
<7. Building Zinx's Read-Write Separation Model>
<8.Zinx Message Queue and Task Worker Pool Design and Implementation>

未完待续...

作者:
不和谐:https://discord.gg/xQ8Xxfyfcz
zinx:https://github.com/aceld/zinx
github:https://github.com/aceld
Aceld的家:https://yuque.com/aceld