DEV Community

Cover image for 7. Building Zinx's Read-Write Separation Model
Aceld
Aceld

Posted on • Updated on

7. Building Zinx's Read-Write Separation Model

[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>
<9. Zinx Connection Management and Property Setting>

[Zinx Application - MMO Game Case Study]

<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>


source code

https://github.com/aceld/zinx/blob/master/examples/zinx_release/zinx-v0.7.tar.gz


In this chapter, we will make a minor modification to Zinx to upgrade it with a new version that separates the read and write operations with the client into two separate Goroutines. One Goroutine will be responsible for reading data from the client, while the other will be responsible for writing data to the client. This design provides benefits such as high cohesion and modularized functionality, making it easier to expand the system's capabilities in the future. The updated read-write architecture is illustrated in Figure 7.1.

Image description

Figure 18.1: Read-Write Separation Model

The server continues to handle client responses and includes the key methods from before, such as Listen() and Accept(). Once a socket connection is established with the client, two Goroutines are launched to handle the read and write operations separately. Messages between the read and write processes are passed through a channel.

7.1 Implementation of Zinx-V0.7 Code

The code modifications for the read-write separation model are as follows. The changes are minimal, involving four areas of improvement.

7.1.1 Adding a communication channel for the read-write modules

First, we need to add a communication channel between the two Goroutines for reading and writing. This channel will be used to transmit signal and status data between the two Goroutines. We define this channel in the Connection struct since both the reading and writing Goroutines will operate on the current connection that has already been established. The updated Connection data structure is as follows:

// 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
}
Enter fullscreen mode Exit fullscreen mode

We add a new channel member, msgChan, to Connection. Its purpose is to facilitate communication between the read and write Goroutines. Additionally, the construction method for Connection needs to initialize the msgChan attribute. The modified code is as follows:

// 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
}
Enter fullscreen mode Exit fullscreen mode

7.1.2 Creating the Writer Goroutine:

We need to write a Goroutine for sending messages to the client. The core code involves invoking conn.Write(), and the logic waits for messages sent by the read Goroutine. The code is as follows:

// 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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

The advantage of creating the StartWriter() Goroutine is that it separates the reading and writing business logic, making the responsibilities clear and allowing for independent optimization of either the read or write process without interfering with each other.

7.1.3 Modifying the Reader to send data to the Channel:

We need to modify the SendMsg() method called by the Reader to send messages to the Writer instead of directly writing back to the client. The relevant code is as follows:

// 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
}
Enter fullscreen mode Exit fullscreen mode

7.1.4 Starting the Reader and Writer:

The logic for starting the connection becomes clearer. We only need to start the Reader and Writer Goroutines separately to handle the connection's read and write operations. The code for starting the connection is as follows:

// 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
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

7.2 Summary

The testing code remains the same as in V0.6, so it will not be reiterated here. We have successfully separated the read and write modules. The significance of the read-write separation design is that it provides a clear direction for future improvements, allowing for targeted optimizations of either the read or write processes. The next step would be to further upgrade based on this read-write separation by adding a task queue mechanism.


[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>
<9. Zinx Connection Management and Property Setting>

[Zinx Application - MMO Game Case Study]

<10. Application Case Study using the Zinx Framework>
<11. MMO Online Game AOI Algorithm>
<12.Data Transmission Protocol: Protocol Buffers>
<13. MMO Game Server Application Protocol>
<14. Building the Project and User Login>
<15. World Chat System Implementation>
<16. Online Location Information Synchronization>
<17. Moving position and non-crossing grid AOI broadcasting>
<18. Player Logout>
<19. Movement and AOI Broadcast Across Grids>


source code

https://github.com/aceld/zinx/blob/master/examples/zinx_release/zinx-v0.7.tar.gz


Author:
discord: https://discord.gg/xQ8Xxfyfcz
zinx: https://github.com/aceld/zinx
github: https://github.com/aceld
aceld's home: https://yuque.com/aceld

Top comments (0)