目录
Day5:完成 Zinx-v1.0 的开发
最近花了较长的时间整理八股文,没有进一步推进 Zinx 项目的开发。今天让我们完成 Zinx-v1.0 整体项目的开发。
今天要完成的任务如下:
- 实现 Zinx 的读写分离模型(之前的 Connection 只有 Reader,读取消息之后写回,现在我们将读写分离,将业务处理得到的结果通过 Writer 写回);
- 实现 Zinx 的消息队列及多任务机制;
- 实现 Zinx 的连接管理;
- 实现 Zinx 的连接属性设置。
Day5-Part1:实现 Zinx 的读写分离模型
接下来我们要对 Zinx 做一些小的改变,具体来说,就是将与客户端进行交互的 Connection 管理的 goroutine 从一个变为两个,一个专门负责从客户端读取数据(Reader),而另一个专门向客户端写数据(Writer)。
读写分类的好处是高内聚、模块功能单一,使得我们之后可以更加方便地拓展功能。
Server 依然用于监听客户端的连接请求,当建立与客户端的套接字之后,就会开启两个 goroutine,分别处理读数据业务和写数据业务,读写数据之间的消息通过 channel 传递。
添加读写模块交互数据的通道
type Connection struct {
Conn *net.TCPConn // 当前连接的 socket TCP 套接字
ConnID uint32 // 当前连接的 ID, 也可称为 SessionID, 全局唯一
isClosed bool // 当前连接的开启/关闭状态
Msghandler ziface.IMsgHandle // 将 Router 替换为消息管理模块
ExitBuffChan chan bool // 告知该连接一经退出/停止的 channel
msgChan chan []byte // 无缓冲 channel, 用于读/写两个 goroutine 之间的消息通信
}
func NewConntion(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),
}
return c
}
我们为 Connection
新增一个 msgChan
成员,它的作用是用于 Reader 和 Writer 两个 goroutine 之间的通信。
创建 Writer Goroutine
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:
//有数据要写给客户端
if _, err := c.Conn.Write(data); err != nil {
fmt.Println("Send Data error:, ", err, " Conn Writer exit")
return
}
case <- c.ExitBuffChan:
//conn已经关闭
return
}
}
}
可以看到,在 Writer Goroutine 当中,开启了一个 for loop,使用 select 等待来自 msgChan 这个通道当中的数据。如果等到了 msgChan 当中的数据,那么就将数据通过c.Conn.Write(data)
写入。
到此我们重新理清一下思路,不管 Connection,还是 Connection 的 Reader 和 Writer,本质上它们都是属于 Server 的。Server 要做的是首先开启服务,监听端口,收到请求之后新建一个 Connection 类型来管理当前这条连接,同时,根据 msgId 将业务与 Connection 进行绑定。
Connection 在建立之后会 Start,同时开启 Reader 和 Writer。Reader 的作用就是阻塞地等待来自 client 的消息,得到消息之后进行拆包,并将得到的消息组装成 request,将 request 发送给处理相应业务的 router。router 的主要方法是 handle,也就是根据得到的消息进行具体的业务处理,在业务处理的过程当中,可能会需要将输出顺着连接写回给 client,这个时候就需要用到 Writer,具体做法是在 Handle 函数当中将业务的输出打包之后输入到通道 msgChan,由于 Writer 同样在阻塞地等待着数据的发送,因此当 msgChan 收到数据之后,Writer 开始工作,将数据通过c.Conn.Write()
写回给 client。
将 Reader 当中直接发送给客户端的数据改为直接发送给 msgChan 这个 channel
func (c *Connection) SendMsg(msgId uint32, data []byte) error {
if c.isClosed == true {
return errors.New("Connection closed when send msg")
}
//将data封包,并且发送
dp := NewDataPack()
msg, err := dp.Pack(NewMsgPackage(msgId, data))
if err != nil {
fmt.Println("Pack error msg id = ", msgId)
return errors.New("Pack error msg ")
}
//写回客户端
c.msgChan <- msg //将之前直接回写给conn.Write的方法 改为 发送给Channel 供Writer读取
return nil
}
启动 Reader 和 Writer
最后,我们需要在 Connection 的 Start 方法当中同时启动 Reader 和 Writer:
// Start 实现 IConnection 中的方法, 它启动连接并让当前连接开始工作
func (c *Connection) Start() {
// 开启处理该连接读取到客户端数据之后的业务请求
go c.StartWriter()
go c.StartReader()
for {
select {
case <-c.ExitBuffChan:
// 得到退出消息则不再阻塞
return
}
}
}
至此,我们便完成了 Zinx 的读写分离。