2

私は最初のGoプログラムであるSMTPサーバーを作成していますが、FSMを使用してネットワークプロトコルの状態遷移を表現するのは賢明だと思いました。私はこのhaskellSMTPFSMの例が本当に好きだったので、その後大まかにモデル化しました。

コンストラクター引数として遷移テーブルを取り、イベントを受け取り、状態テーブルで一致する適切なハンドラー関数を呼び出すRunメソッドを持つ単純なFSMタイプを作成しました。次に、接続からの着信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内で定義する必要があります。この遷移表をすべてのセッションに対して1回だけ定義し、必要なセッションハンドラー関数に適切にアクセスできる方法を考えることはできません。

このプログラムを単純な手続き型で作成した場合、これらの問題は発生しません。FSMは、すべてのハンドラー関数に直接アクセスできます。

私が考えることができる唯一のことは、関数ポインターを返さないようにFSMを変更することですが、代わりに、セッションが適切な関数にマップする任意の定数を返します。

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へのリンクです。

4

2 に答える 2

3

ハンドラーをメソッドにせず、代わりにインスタンスをパラメーターとして受け取る関数を使用すると、この全体がはるかに簡単になります。これはおおむね同じことですがtype State func(Session) State、セットアップのタイプがあれば、より簡単に考えることができます。

于 2012-11-03T01:30:35.563 に答える
2

一般的なアイデアがどれほどハックなのかわかりません。関心の分離により、ステート マシンは単にトークンを発行するだけで、後でそれがどのように使用されるかを認識しません。メソッド内でhandleInputは、そのトークンを使用してアクションを実行します。この場合は、セッション オブジェクトで適切なメソッドを見つけることです。

しかし、私にはすべてが終わりに思えます。を認識したい遷移表がありSessionます。fsm私が推測しているパッケージはほとんどありませんがSession、パッケージの遷移テーブルに依存しているため、注意が必要です。

リンクを切断して、適切なメソッドを見つけるために使用できる aconstを発行するSessionか、より手続き的なルートに進むか、ビットをより近くにマージします (おそらく遷移テーブルを捨てることを含みます)。

または、ハックのように非効率的なルートに本当に行きたい場合はfindHandler、適切なメソッドを名前で反映させ、そのswitchステートメントを切り取ってみましょう。ただし、楽しみのためだけです:)

また、実装のためにいくつかの代替案を検討することもできます。標準ライブラリには素晴らしい例がたくさんあります。テキスト/テン​​プレートのパッケージ ファイルをチェックアウトします。

http://golang.org/src/pkg/text/template/parse/lex.go

http://golang.org/src/pkg/text/template/parse/parse.go

私が言っていることの要点は、すでに述べたように、関数の外部で定義された遷移テーブルが必要な場合は、メソッドではなく、トークンまたは関数を参照する必要があるということですあなたが持っていないインスタンス。しかし、楽しみのために、以下のようなものを使用してAction("sayOk")、遷移テーブルで言って、Session配信された結果の関数に渡すことができます。

package main

import (
    "fmt"
    "reflect"
)

type Foo struct{}

func (f *Foo) Bar() string {
    return "hello"
}

func Action(name string) func(f *Foo) string {
    return func(f *Foo) string {    
        s := reflect.ValueOf(f).MethodByName(name).Call([]reflect.Value{})
        return s[0].String()
    }
}


func main() {
    f := &Foo{}
    a := Action("Bar")
    fmt.Println(a(f))
}
于 2012-11-02T08:36:51.020 に答える