我正在编写我的第一个 Go 程序,一个 SMTP 服务器,我认为使用 FSM 来表示网络协议的状态转换会很优雅。我真的很喜欢这个 haskell SMTP FSM 示例,所以我在此之后对其进行了松散的建模。
我创建了一个简单的 FSM 类型,它接受一个转换表作为其构造函数参数,并有一个 Run 方法,该方法接受一个事件并将调用它在状态表中匹配的适当处理函数。然后我有一个“会话”类型,这是在处理来自其连接的传入 SMTP 命令后需要使用 FSM 的类型。
这是 FSM 转换的样子:
type Transition struct {
from State
event Event
to State
handler func() string
}
然后,在我的 Session 对象中,我被迫在其构造函数中定义转换表,以便我可以访问其用于转换操作的方法:
func (s *SmtpSession) NewSession() {
transitions := []Transition{
{Initial, Rset, Initial, sayOk},
{HaveHelo, Rset, HaveHelo, sayOk},
{AnyState, Rset, HaveHelo, resetState},
...
{Initial, Data, Initial, needHeloFirst},
{HaveHelo, Data, HaveHelo, needMailFromFirst},
{HaveMailFrom, Data, HaveMailFrom, needRcptToFirst},
{HaveRcptTo, Data, HaveData, startData},
}
smtpFsm = StateMachine.NewMachine(transitions)
}
当所有会话将具有基本相同的 FSM 时,必须在每个会话中创建此 FSM 的实例似乎很浪费。我宁愿只拥有某种“静态” FSM,它可以被赋予一个转换表,然后 Run 方法将采用当前状态和一个事件并返回结果“动作”函数。
然而,这就是我遇到麻烦的地方。因为所有的处理函数实际上都是 Session 对象的方法,所以我必须在 Session 中定义它。我想不出一种方法可以只为所有会话定义一次转换表,并且仍然可以适当地访问我需要的会话处理程序函数。
如果我以直接的程序风格编写这个程序,那么我就不会遇到任何这些问题。FSM 可以直接访问所有处理函数。
我唯一能想到的是将我的 FSM 更改为不返回函数指针,而是返回一些任意常量,然后 Session 将映射到适当的函数:
var transitions = []Transition{
{Initial, Rset, Initial, "sayOk"},
{HaveHelo, Rset, HaveHelo, "sayOk"},
{AnyState, Rset, HaveHelo, "resetState"},
...
}
var smtpFsm = NewStateMachine(transitions)
func (s *Session) handleInput(cmd string) {
event := findEvent(cmd)
handler := findHandler(smtpFsm.Run(s.currentState, event))
handler(cmd)
}
func (s *Session) findHandler(handlerKey string) {
switch handlerKey {
case "sayOk":
return sayOk
case "resetState":
return resetState
}
}
这将解决必须为每个会话重新初始化一个新的 FSM 的问题,但它也感觉有点 hackish。有人对我如何避免这个问题有任何建议吗?这是演示问题的不完整 Session.go 的链接。