Zinx framework learning - separation of read and write coroutines

Zinx - V0.7 Separation of read and write coroutines

  • The previous connection used StartReader for client data
  • Next, we will make a small change to Zinx, that is, the Gouroutine that interacts with the client to learn data will be changed from one Gouroutine to two, one dedicated to reading data from the client, and one dedicated to sending data to the client. Client writes data. What are the benefits of this design? Of course, the purpose is high cohesion, and the function of the module is single.
  • The server is still processing the response from the client, and the key methods are Listen, Accept, and so on. When the socket with the client is established, two Goroutines will be opened to process the data reading business and the data writing business respectively, and the messages between reading and writing data are transmitted through a Channel

 Implementation ideas

  • connection.go

Connection definition to add channel

type Connection struct {
	//当前链接的socket TCP套接字
	Conn *net.TCPConn

	//链接的ID
	ConnID uint32

	//当前的链接状态
	isClosed bool

	//告知当前链接已经退出的/停止 channel(由Reader告知Writer退出)
	ExitChan chan bool

	//无缓冲d管道,用于读、写Goroutine之间的消息通信
	msgChan chan []byte

	//消息的管理MsgID 和对应的处理业务API关系
	MsgHandler ziface.IMsgHandle
}

Initialize the link method to increase the channel

//初始化链接模块的方法
func NewConnection(conn *net.TCPConn, connID uint32, msgHandler ziface.IMsgHandle) *Connection {
	c := &Connection{
		Conn:       conn,
		ConnID:     connID,
		MsgHandler: msgHandler,
		isClosed:   false,
		msgChan:    make(chan []byte),
		ExitChan:   make(chan bool, 1),
	}
	return c
}

Add StartWriter method

//写消息Goroutine, 专门发送给客户端消息的模块
func (c *Connection) StartWriter() {
	fmt.Println("[Writer Goroutine is running]")
	defer fmt.Println("[conn Writer exit!]", c.RemoteAddr().String())

	//不断的阻塞的等待channel的消息,进行写给客户端
	for {
		select {
		case data := <-c.msgChan:
			//有数据要写给客户端
			if _, err := c.Conn.Write(data); err != nil {
				fmt.Println("Send data error, ", err)
				return
			}
		case <-c.ExitChan:
			//代表Reader已经退出,此时Writer也要推出
			return
		}
	}
}

Use the for loop to continuously block waiting for the message of the channel and write it back to the client. The exit must be told by the Reader to the Writer to exit, because we can know that the Reader must have an exception when the client exits.

Modify the Start method to separate the read and write goroutines

//启动链接 让当前的链接准备开始工作
func (c *Connection) Start() {
	fmt.Println("Conn Start() ... ConnID = ", c.ConnID)
	//启动从当前链接的读数据的业务
	go c.StartReader()
	//启动从当前链接写数据的业务
	go c.StartWriter()
}

In SendMsg, the data is sent to the client through the channel

//提供一个SendMsg方法 将我们要发送给客户端的数据,先进行封包,再发送
func (c *Connection) SendMsg(msgId uint32, data []byte) error {
	if c.isClosed == true {
		return errors.New("Connection closed when send msg")
	}

	//将data进行封包 MsgDataLen|MsgID|Data
	dp := NewDataPack()

	//MsgDataLen|MsgID|Data
	binaryMsg, err := dp.Pack(NewMsgPackage(msgId, data))
	if err != nil {
		fmt.Println("Pack error msg id = ", msgId)
		return errors.New("Pack error msg")
	}

	//将数据发送给客户端
	c.msgChan <- binaryMsg
	return nil
}

The Stop method is added to tell the Writer to close

//停止链接 结束当前链接的工作
func (c *Connection) Stop() {
	fmt.Println("Conn Stop().. ConnID = ", c.ConnID)

	//如果当前链接已经关闭
	if c.isClosed == true {
		return
	}
	c.isClosed = true

	//关闭socket链接
	c.Conn.Close()

	//告知Writer关闭
	c.ExitChan <- true

	//回收资源
	close(c.ExitChan)
	close(c.msgChan)
}

When the Reader exits, write data to ExitChan, only Reader will break if there is an error, and then call the Stop method, so we can send this message to the Writer in the Stop function

the whole idea

Add a pipeline for reading and writing Goroutine communication in the Connection structure, initialize the pipeline in NewConnection, do some business processing in StartReader(), send data after reading the data and call the DoMsgHandler method, and then in the DoMsgHandler method The Handle method will be called. When the server calls the Handle method, it will call the SendMsg method to send the message to the client. In the past, it was directly written back to the client. Now it sends the message to the msgChan pipeline. At this time, StartWriter() will get the data transmitted by the pipeline. Then send it to the client. When exiting, StartReader detects that the client has exited, because it will block and wait for requests according to the connection. After the client exits, it will break out of the for loop, and then call the Stop method. In the Stop method, close the connection, tell the Writer to exit and recycle resources, and then StartWriter will exit when it gets the message

Guess you like

Origin blog.csdn.net/qq_47431008/article/details/131032274