State machine mode

Article Directory

1. Definition

GOF Definition
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.
Allows an object to change its behavior when its internal state changes.This object appears to have modified its class.

State machine mode is often used to implement finite state machines.

Finite State Machine is short for State Machine (FSM).
A State machine has three components: State, Event, and Action.
The three relationships are that when an event occurs, it will decide what action to perform based on the state and then move to the next state.

State Machine mode means that events are handled in different classes in different states.

The state machine mode is only one way to implement a finite state machine. Logical judgment and lookup are two other common ways to implement a finite state machine

The following three methods are described and compared briefly.

2. Implementation

2.1 Logical judgment.

Write a large processing function in which you can determine what the current state is, what events are going on, and what actions are going to be performed.

void proceess(int event) {
   if (state==S1) {
       if (envent==E1)
        {
            doAction()
        }
    }
    else
    {
        ...
    }
}

This is probably the most common method we use.There will be a lot of if-else or switch in the code. If the state machine is simple and implemented in this way, it is not bad, but when the state machine is complex, it may be prone to error and the code is not easy to maintain.

2.2 Table Lookup Method

You can write the current state, what events you receive, what actions you perform, and what state you enter in the Table

For instance:
For example, there is a state machine with three states s1, s2, s3, three events e1, e2, e3, three actions action1, action2, action3

The relationship between state and action is:
Switch to e1, execute action1, switch to e2, execute action2, switch to e3, execute action3

The relationship between state and event is:
S1 receives event e1 e2 e3, switching to s1, s3, s2
S2 receives event e1 e2 e3, switching to s2, s1, s3 respectively
S3 receives event e1 e2 e3, switching to s3, s2, s1

Use tables to describe state event state relationships:

e1 e2 e3
s1 s1 /action1 s3/action3 s2/action2
s2 s2/action2 s1/action1 s3/action3
s3 s3/action3 s2/action2 s1/action1

The state machine described above can be implemented as follows:

//sbkun
#include <iostream>
using namespace std;

enum event{
  e1=1,
  e2=2,
  e3=3,
};

enum state{
  s0=0,
  s1=1,
  s2=2,
  s3=3,
};

//Since array subscripts start at 0, I want e1=1 instead of 0 for better output
//So use s0 as a placeholder so that e1=1
state tranTable [][4]= {
      //e1, e2, e3
  {s0, s0, s0, s0},
  {s0, s1, s3, s2}, //S1 This line indicates that S1 receives event e1 e2 e3, switching to s1, s3, s2, respectively
  {s0, s2, s1, s3}, //S2 This line indicates that S2 receives event e1 e2 e3, switching to s2, s1, s3 respectively
  {s0, s3, s2, s1}  //S3 This line indicates that S3 receives event e1 e2 e3, switching to s3, s2, s1, respectively
};

typedef const char*(*action)();

const char* action1(){
  return "action1";
}

const char* action2(){
  return "action2";
}

const char* action3(){
  return "action3";
}

//Here NULL is also a placeholder, so that action1, at actionTable[1][1]
action actionTable[][4] {
  {NULL, NULL, NULL, NULL}, 
  {NULL, action1, action3, action2}, 
  {NULL, action2, action1, action3},
  {NULL, action3, action2, action1}
};

void print(state src, event e) {
  printf("state [%d] recvd [%d] goto [%d] action[%s]\n",
  src, e, tranTable[src][e], actionTable[src][e]());
}

int main() {
  print(s1, e1);
  print(s1, e2);
  print(s1, e3);
  printf("=========\n");

  print(s2, e1);
  print(s2, e2);
  print(s2, e3);
  printf("=========\n");

  print(s3, e1);
  print(s3, e2);
  print(s3, e3);
  printf("=========\n");
}	

#Output, consistent with the state event action relationship described in the table
[sbkun:designpattern]$ ./statedemo2
state [1] recvd [1] goto [1] action[action1]
state [1] recvd [2] goto [3] action[action3]
state [1] recvd [3] goto [2] action[action2]
=========
state [2] recvd [1] goto [2] action[action2]
state [2] recvd [2] goto [1] action[action1]
state [2] recvd [3] goto [3] action[action3]
=========
state [3] recvd [1] goto [3] action[action3]
state [3] recvd [2] goto [2] action[action2]
state [3] recvd [3] goto [1] action[action1]

Look-up method, compared to logical judgment, the code is cleaner, clearer, and error-free.Tables can be stored in memory, as well as in configuration files, databases, and in general, they are flexible.The only problem with table lookup may be how to design a table that is useful and maintained.

2.3 State Machine Mode

My understanding of state machine mode:

For each state, a common parent class is abstracted, defined in the parent class, and the interface for handling various events is defined. Then for each specific state, a corresponding subclass is implemented, and the subclass is used to handle various events.

The tcp state machine is probably the most well-known state machine model


The following sample code implements four states: CLOSED, LISTEN, SYN-RECEIVED, ESTABLISHED.
The implementation of these four states (of course, simply expressing meaning, not functionality) has taken 180 lines of code, and the entire state has been completed, which is estimated to be handicapped...

Explanation of the code:

  • TCPState is an abstract state class that defines an interface for handling various events and provides a default implementation (the event is ignored by default)

  • Subclass such as TCPListen, which is a state-specific class that overrides the parent class's corresponding interface if there are events that need to be handled.

  • When implementing these subclasses, considering that there are no variable data members in these subclasses, the singleton mode is used to improve efficiency.
    Considering the difficulty of writing a singleton for each subclass, another singleton template class was written for code reuse.See SingleTon.h for the code

  • TCPConnection is the state holder, corresponding to a true tcp connection

2.3.1 cpp implementation

//author sbkun
#include "SingleTon.h"

#include <stdio.h>
#include <unistd.h>
#include <string>
#include <iostream>

using namespace std;

class TCPState;

class TCPConnetion
{
	public:
		TCPConnetion();
		~TCPConnetion() {}

		//used for client
		void ActiveOpen();
		//used for server
		void PassiveOpen();
		void Close();

		void Send(string& msg);
		void Recv(const string& msg);

	private:
		friend class TCPState;
		void ChangeState(TCPState*);

	private:
		TCPState* state_;
};

class TCPState
{
	public:
		virtual void ActiveOpen(TCPConnetion* ) {}
		virtual void PassiveOpen(TCPConnetion* ) {}
		virtual void Close(TCPConnetion*)	{}
		virtual void Send(TCPConnetion*, string& msg) {}
		virtual void Recv(TCPConnetion*, const string& msg) {}

		virtual ~TCPState() = default;
		virtual const string& getStateName() const = 0;

	protected:
		void ChangeState(TCPConnetion*, TCPState*);
};

class TCPListen: public TCPState, public SingleTon<TCPListen>
{
	public:
		void Send(TCPConnetion* c, string& msg) override;
		void Recv(TCPConnetion* c, const string& msg) override;
		const string& getStateName() const override {
			return name_;
		}
	private:
		const string name_ = "LISTEN";
};

class TCPEstablished : public TCPState, public SingleTon<TCPEstablished>
{
	public:
		const string& getStateName() const override{
			return name_;
		}
	private:
		const string name_ = "ESTABLISHED";
};

class TCPClosed : public TCPState, public SingleTon<TCPClosed>
{
	public:
		void PassiveOpen(TCPConnetion* c) override {
			ChangeState(c, &TCPListen::getInstance());
		}

		const string& getStateName() const override{
			return name_;
		}
	private:
		const string name_ = "CLOSED";

};

class TCPSynReceived: public TCPState, public SingleTon<TCPSynReceived> 
{
	public:
		void Recv(TCPConnetion* c, const string& msg) override {
			if (msg=="ACK")	 {
				ChangeState(c, &TCPEstablished::getInstance());
			}
		}

		const string& getStateName() const override {
			return name_;
		}
	private:
		const string name_ = "SYN-RECEIVED";
};

void TCPListen::Send(TCPConnetion* t, string& msg)
{
	cout<<"Send "<<msg<<endl;
	ChangeState(t, &TCPSynReceived::getInstance());
}

void TCPListen::Recv(TCPConnetion* c, const string& msg) {
	if (msg == "ACK") {
		string msg = "SYN+ACK";
		Send(c, msg);
	}
}

TCPConnetion::TCPConnetion()
{
	state_ = &TCPClosed::getInstance();
	cout<<"TCPConnetion init state "<<state_->getStateName()<<endl;
}

void TCPConnetion::ChangeState(TCPState* s)
{
	state_ = s;
}

void TCPConnetion::ActiveOpen()
{
	state_->ActiveOpen(this);
}

void TCPConnetion::PassiveOpen()
{
	cout<<"TCPConnetion setup tcb"<<endl;
	state_->PassiveOpen(this);
}

void TCPConnetion::Close()
{
	state_->Close(this);
}

void TCPConnetion::Send(string& msg)
{
	state_->Send(this, msg);
}

void TCPConnetion::Recv(const string& msg)
{
	cout<<"TCPConnetion Recvd [ "<<msg<<" ]"<<endl;
	state_->Recv(this, msg);
}

void TCPState::ChangeState(TCPConnetion* t, TCPState* s)
{
	cout<<"[ "<<getStateName()<<" ] goto "<<"[ "<<s->getStateName()<<"] "<<endl<<endl;
	t->ChangeState(s);
}

int main(int argc, char *argv[])
{
	TCPConnetion c;
	c.PassiveOpen();
	c.Recv("ACK");	
	c.Recv("ACK");
	return 0;
}
#pragma once
template <class T>
class SingleTon
{
public:
    static T &getInstance()
    {
        static T t;
        return t;
    }
    SingleTon(const SingleTon &) = delete;
    SingleTon(SingleTon &&) = delete;
    SingleTon &operator=(const SingleTon &) = delete;
    SingleTon &operator=(SingleTon &&) = delete;

protected:
    virtual ~SingleTon()  = default;
    SingleTon() noexcept  = default;
};
#results of enforcement
[sbkun:designpattern]$ ./statedemo
TCPConnetion init state CLOSED
TCPConnetion setup tcb
[ CLOSED ] goto [ LISTEN]

TCPConnetion Recvd [ ACK ]
Send SYN+ACK
[ LISTEN ] goto [ SYN-RECEIVED]

TCPConnetion Recvd [ ACK ]
[ SYN-RECEIVED ] goto [ ESTABLISHED]

2.3.2 java implementation

//sbkun
package designpattern.state;

//tcp connection, holding tcp state machine
class TCPConnetion {
    TCPConnetion() {
        this.state = new TCPClosed();
        System.out.println("TCPConnetion init state " + state.getStateName());
    }

    void PassiveOpen() {
        System.out.println("TCPConnetion setup tcb");
        this.state.PassiveOpen(this);
    }

    void Close() {
        state.Close(this);
    }

    void Send(String msg) {
        state.Send(this, msg);
    }

    void Recv(String msg) {
        System.out.println("TCPConnetion Recvd [ " + msg + " ]");
        state.Recv(this, msg);
    }

    void ChangeState(TCPState s) {
        this.state = s;
    }

    private TCPState state;
};

//tcp state machine base class that handles events in a specific state
//Each interface represents the handling of an event
abstract class TCPState {
    protected TCPState(String name) {
        this.name = name;
    }

    //Actively open connection for client
    void ActiveOpen(TCPConnetion c) {
    }

    //Passive open connection for server
    void PassiveOpen(TCPConnetion c) {
    }

    //Close Connection
    void Close(TCPConnetion c) {

    }

    //Send out message
    void Send(TCPConnetion c, String msg) {

    }

    //Received Message
    void Recv(TCPConnetion c, String msg) {
    }

    //Get the current status name
    String getStateName() {
        return name;
    }

    void ChangeState(TCPConnetion c, TCPState s) {
        System.out.println("[" + getStateName() + "] goto [" + s.getStateName() + "] \n");
        c.ChangeState(s);
    }

    private String name;
};

class TCPListen extends TCPState {
    TCPListen() {
        super("LISTEN");
    }

    @Override
    void Send(TCPConnetion c, String msg) {
        System.out.println("Send " + msg);
        ChangeState(c, new TCPSynReceived());
    }

    @Override
    void Recv(TCPConnetion c, String msg) {
        if (msg == "ACK") {
            Send(c, "SYN+ACK");
        }
    }
};

class TCPEstablished extends TCPState {
    TCPEstablished() {
        super("ESTABLISHED");
    }
};

class TCPClosed extends TCPState {
    TCPClosed() {
        super("CLOSED");
    }

    @Override
    void PassiveOpen(TCPConnetion c) {
        ChangeState(c, new TCPListen());
    }
};

class TCPSynReceived extends TCPState {
    TCPSynReceived() {
        super("SYN-RECEIVED");
    }

    @Override
    void Recv(TCPConnetion c, String msg) {
        if (msg == "ACK") {
            ChangeState(c, new TCPEstablished());
        }
    }
};

public class StateDemo {
    public static void main(String[] args) {
        TCPConnetion c = new TCPConnetion();
        c.PassiveOpen();    //tcp connection entered listen state
        c.Recv("ACK"); //Receive ack for first-step handshake
        c.Recv("ACK"); //Receive the ack of the third handshake and the connection is established successfully
    }
}

State Machine mode, in which the processing of various events in each state is dropped into subclasses for implementation.So when implementing each subclass, I just need to think about that state.
How to handle events is less error-prone and maintainable than logical branching.
The disadvantage is that for each specific state, a subclass is implemented, and if there are too many classes, the code will not be maintained.
So the state machine mode works for scenarios where there are not many states, but each state is handled in a complex way

3. Summary:

if-else logic judgment: for scenarios where the state machine is simple and complex,
The code is error prone and not maintainable.

Lookup table method: clear logic, good maintenance.State machines are simple and can be used when they are complex.
The question is how to design the right table to use

State Machine Mode: Suitable for scenarios where there are fewer states, but the processing of each state is complex.After all, we should implement a class for each state. There are many states, handicapped, classified and maintained brain handicapped.

4. Full code address

All the code for this column has been uploaded to github. I hope you can criticize and correct it a lot
git link

Tags: Java less github

Posted on Thu, 04 Jun 2020 12:52:57 -0400 by scopley