记录link包的重构(2)

Go设计分享 by 达达 at 2014-08-21

今天又对link包得API做了一次重构,这次重构的目的是提升Session和Server类型的API灵活性。

我们先来看重构之前link包的唯一用法:

server.Handle(func(session *Session) {
    fmt.Println("session start")

    session.OnMessage(func(session *Session, msg []byte) {
        fmt.Printf("new message: %s\n", msg)
    })

    session.OnClose(func(session *Session, reason error) {
        fmt.Println("session closed")
    })

    session.Start()
})

上一个版本的API设计有以下几个问题:

  1. Server不能可控的Accept,比如Accept特定的连接数之后就暂停Accept
  2. Session不能可控的接收消息,比如实现某种握手协议时需要顺序收发N个消息包,异步结构的代码没有顺序结构的清晰

因此我对Session和Server的API做了以下修改:

  1. Server中添加了Accept方法,每次调用只返回一个新Session,就像Listener的Accept那样。
  2. Session中添加了Read方法,每次调用只返回一个消息,就像Conn的Read那样,配合SyncSend就可以实现顺序收发。

这两个方法一开始是直接替代了Handle和OnMessage,导致link包的用法变成这样:

for {
    session, err := server.Accept()
    if err != nil {
        break
    }

    go func() {
        fmt.Println("session start")

        for {
            msg := session.Read()
            if msg == nil {
                break
            }
            fmt.Printf("new message: %s\n", msg)
        }

        fmt.Println("session closed")
    }()
}

API变得原始很多,看起来没之前那么方便了。

于是我又给Server和Session加上了两个方法,AcceptLoop和ReadLoop,但是比之前的结构简化一些,Session不需要OnClose事件回调了。

server.AcceptLoop(func(session *Session) {
    fmt.Println("session start")

    session.ReadLoop(func(_ *Session, msg []byte) {
        fmt.Printf("new message: %s\n", msg)
    })

    fmt.Println("session closed")
})

AcceptLoop在接收到新连接时会创建一个新的goroutine执行回调函数,所以这个回调函数就可以作为session处理消息的循环使用,ReadLoop会一直循环进行Read,就像第二段代码中那样。

经过这次重构,link就可以像之前一样用,又可以在需要的时候进行精细控制。