我正在写我的第一个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方法将获取当前状态和一个事件,并返回结果“ action”函数。
但是,那是我遇到麻烦的地方。因为所有处理程序函数实际上都是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的问题,但同时也会感到有些骇人听闻。有人对我如何避免此问题有任何建议吗?这是一个指向不完整的Session.go的链接,该链接演示了此问题。
慕村9548890
相关分类