用GO实现的erlang的genfsm.

sll1983 · · 3617 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

关于erlang的有限状态机,参考erlang四大behaviour之二-gen_fsm这篇文章。

有限状态机可以用下面这个公式来表达

State(S) x Event(E) -> Actions(A), State(S')

这两天正好空闲,就用golang实现了一下,话不多说,直接上代码

package util

import (
	"errors"
	"reflect"
	"sync"
	"time"
	"unicode"
	"unicode/utf8"
	"util/log"
)

var typeOfError = reflect.TypeOf((*error)(nil)).Elem()

type FSM struct {
	sync.Mutex
	StopReason string
	rcvr       reflect.Value // receiver of methods for the service
	typ        reflect.Type  // type of the receiver
	method     map[string]reflect.Method
	event      chan Event
	quit       chan int
	state      string
	stopped    bool
}

type Event struct {
	event   string
	param   interface{}
	timeout int
}

func (fsm *FSM) IsStopped() bool {
	fsm.Lock()
	defer fsm.Unlock()
	return fsm.stopped
}

func (fsm *FSM) SendEvent(event string, param interface{}) {
	fsm.Lock()
	defer fsm.Unlock()
	if fsm.stopped {
		return
	}

	fsm.event <- Event{event, param, 0}

}

func (fsm *FSM) Init(start string) error {
	if _, ok := fsm.method[start]; !ok {
		return errors.New("not found state")
	}
	fsm.state = start
	go func() {
		for {
			select {
			case e := <-fsm.event:
				go fsm.CallState(e)
			case <-fsm.quit:
				goto close
			}
		}
	close:
		close(fsm.event)
		close(fsm.quit)
	}()

	return nil
}

func (fsm *FSM) CallState(e Event) {
	fsm.Lock()
	defer fsm.Unlock()
	if function, ok := fsm.method[fsm.state]; ok {
		returnValues := function.Func.Call([]reflect.Value{fsm.rcvr, reflect.ValueOf(e.event), reflect.ValueOf(e.param), reflect.ValueOf(e.timeout)})
		nextstate := returnValues[0].String()
		timeout := returnValues[1].Int()
		errInter := returnValues[2].Interface()
		errmsg := ""

		if errInter != nil {
			errmsg = errInter.(error).Error()
		}

		if nextstate == "stop" {
			fsm.Stop(errmsg)
			fsm.quit <- 1
			return
		}

		if errmsg != "" {
			log.LogError(errmsg)
		}

		fsm.state = nextstate

		if timeout > 0 {
			go fsm.DelayCall(time.Duration(timeout))
		}
	}
}

func (fsm *FSM) DelayCall(timeout time.Duration) {
	select {
	case <-time.After(timeout * time.Millisecond):
		fsm.event <- Event{"timeout", 0, int(timeout)}
	}
}

func (fsm *FSM) Stop(message string) {
	fsm.StopReason = message
	fsm.stopped = true
}

func (fsm *FSM) Close() {
	fsm.Lock()
	defer fsm.Unlock()
	if fsm.stopped {
		return
	}

	fsm.quit <- 1
}

func NewFSM(fsm interface{}) *FSM {
	f := &FSM{typ: reflect.TypeOf(fsm), rcvr: reflect.ValueOf(fsm), event: make(chan Event), quit: make(chan int)}
	f.method = suitableMethods(f.typ, true)
	return f
}

func isExported(name string) bool {
	rune, _ := utf8.DecodeRuneInString(name)
	return unicode.IsUpper(rune)
}

func suitableMethods(typ reflect.Type, reportErr bool) map[string]reflect.Method {
	methods := make(map[string]reflect.Method)
	for m := 0; m < typ.NumMethod(); m++ {
		method := typ.Method(m)
		mtype := method.Type
		mname := method.Name

		if !isExported(mname) {
			continue
		}

		// Method needs four ins: receiver, string, interface{}, int.
		if mtype.NumIn() != 4 {
			if reportErr {
				log.LogError("method", mname, "has wrong number of ins:", mtype.NumIn())
			}
			continue
		}

		// First arg must be a string.
		if mtype.In(1).Kind() != reflect.String {
			if reportErr {
				log.LogError("method", mname, "arg1 type not a string:", mtype.In(1).Kind())
			}
			continue
		}
		// Second arg must be a interface.
		if mtype.In(2).Kind() != reflect.Interface {
			if reportErr {
				log.LogError("method", mname, "arg2 type not a interface:", mtype.In(2).Kind())
			}
			continue
		}

		// Third arg must be a int.
		if mtype.In(3).Kind() != reflect.Int {
			if reportErr {
				log.LogError("method", mname, "arg3 type not a int:", mtype.In(3).Kind())
			}
			continue
		}

		// Method needs three out.
		if mtype.NumOut() != 3 {
			if reportErr {
				log.LogError("method", mname, "has wrong number of outs:", mtype.NumOut())
			}
			continue
		}

		if mtype.Out(0).Kind() != reflect.String {
			if reportErr {
				log.LogError("method", mname, "out1 type not a string:", mtype.Out(0).Kind())
			}
			continue
		}
		if mtype.Out(1).Kind() != reflect.Int {
			if reportErr {
				log.LogError("method", mname, "out1 type not a int:", mtype.Out(1).Kind())
			}
			continue
		}
		if mtype.Out(2) != typeOfError {
			if reportErr {
				log.LogError("method", mname, "out3 type not a error:", mtype.In(2).Kind())
			}
			continue
		}
		methods[mname] = method
	}
	return methods
}

下面就是使用方法:

type GoFSM struct {
}

func (f *GoFSM) State1(event string, param interface{}, t int) (nextstate string, timeout int, err error) {
	log.LogMessage(event, param.(int))
	return "State2", 100, nil //如果timeout大于0,则在timeout毫秒后,自动调用下一个状态,下一个状态的event为timeout
}

func (f *GoFSM) State2(event string, param interface{}, t int) (nextstate string, timeout int, err error) {
	log.LogMessage(event)
	return "stop", 0, errors.New("stop ok") //nextstate=stop则停止状态机,err为停止原因
}

func main() {
	f := util.NewFSM(&GoFSM{})
	f.Init("State1")     //初始状态
	f.SendEvent("Do", 1) //发送事件
	time.Sleep(time.Second * 1)
}

所有的状态回调函数,必须以大写字母开头,原型必须是

func(event string, param interface{}, t int) (nextstate string, timeout int, err error) 

event是事件名,param为事件的参数,t>0表示这是一个延时事件。返回值:nextstate为新的状态,必须和状态回调函数同名,如果为"stop"则表示没有后续的状态,状态机停止。timeout>0表示延时回调,将在timeout时间后,产生一个timeout事件。

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:sll1983

查看原文:用GO实现的erlang的genfsm.

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3617 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传