CS 596: Client Server Programming
State, Classes, Modularity
[To Lecture Notes Index]
San Diego State University -- This page last updated March 20, 1995

Contents of State, Classes, Modularity Lecture
- State Machine Implementation
- Switch
- Table
- State Classes
- First Pass
- Storing Information
- Reducing Time Spent Creating State Objects
- Parsing the Input Part 1
- Parsing the Input Part 2 - Who reads input?
- Modularity
- Sample Main Program
Three general methods:
- Switch
- Table
- State Classes
Sample protocol: SPOP (Simple POP)
Commands allowed:
- USER
- PASS
- LIST
- QUIT
switch (state)
case 1:
switch (input)
case USER: state = 2; break;
case QUIT: state = 4; break;
default: state = 5; break;
case 2:
switch (input)
case PASS: state = 3; break;
case QUIT: state = 4; break;
default: state = 5; break;
case 3:
switch (input)
case LIST: state = 3; break;
case QUIT: state = 4; break;
default: state = 5; break;
case 4:
state = 4; break;
case 5:
switch (input)
case QUIT: state = 4; break;
default: state = 5; break;
Input State
1 2 3 4 5
USER 2 5 5 4 5
PASS 5 3 5 4 5
LIST 5 5 3 4 5
QUIT 4 4 4 4 4
typedef struct
{
char *command;
int (*function)();
int valid_in;
int win;
int lose;
} vector;
vector switchtable[] =
{
{"user", user, AUTH1, AUTH2, AUTH1},
{"pass", pass, AUTH2, TRANS, AUTH},
.
.
.
{NULL, NULL, 0, 0, 0}
};
SPop Class
class SPop {
public:
SPop();
void USER(); void PASS();
char* LIST(); void QUIT();
private:
friend class SPopState;
void ChangeState (SPopState* NewState)
SPopState* CurrentState;
};
SPop :: SPop () {
CurrentState = new Start();
}
void SPop :: ChangeState (SPopState* NewState) {
CurrentState = NewState;
}
void SPop :: USER () {
CurrentState -> USER( this )
}
void SPop :: PASS () {
CurrentState -> PASS ( this )
}
char* SPop :: LIST () {
return CurrentState -> LIST ( this )
}
void SPop :: QUIT () {
CurrentState -> QUIT ( this )
}
SPopState
Define the default operations for each input
class SPopState {
public:
virtual void USER ( SPop* Context );
virtual void PASS ( SPop* Context );
virtual char* LIST ( SPop* Context );
virtual void QUIT ( SPop* Context );
}
void SPopState :: USER ( SPop* Context ) {
Context -> ChangeState( new Error() );
}
void SPopState :: PASS ( SPop* Context ) {
Context -> ChangeState( new Error() );
}
void SPopState :: LIST ( SPop* Context ) {
Context -> ChangeState( new Error() );
}
void SPopState :: QUIT ( SPop* Context ) {
Context -> ChangeState( new Quit() );
}
The Real State Classes
Start
class Start : public SPopState {
public:
virtual void USER ( SPop* Context );
}
void Start :: USER ( SPop* Context ) {
Context -> ChangeState( new Authenticate() );
}
Authenticate
class Authenticate : public SPopState {
public:
virtual void PASS ( SPop* Context );
}
void Authenticate :: PASS ( SPop* Context ) {
Context -> ChangeState( new List() );
}
List
class List : public SPopState {
public:
char* List ( SPop* Context );
}
char* List :: List ( SPop* Context ) {
Get and return list
}
Error
class Error : public SPopState {
void QUIT ( SPop* Context );
}
void Error :: QUIT ( SPop* Context ) {
Context -> ChangeState( new Quit() );
}
Quit
class Error : public SPopState {
void QUIT ( SPop* Context );
}
Get Real This Doesn't Do Anything!
How does this interface with the program?
Where does the input come from?
Who parses the input?
Where does the output go?
What if different states can accept different inputs?
How do the state classes store information (user name)?
How does one "end the protocol"?
Don't you create a lot of state objects needlessly?
- Each SPop state class ( Start, Error, ...) can store data
- Or store data in SPop class
Example: User name and password
class SPop {
public:
SPop(); void USER(); void PASS();
char* LIST(); void QUIT();
private:
char* User;
char* Password;
friend class SPopState;
void ChangeState (SPopState* NewState)
SPopState* CurrentState;
};
class Start : public SPopState {
public:
virtual void USER (char* UserName, SPop* Context );
}
void Start :: USER (char* UserName, SPop* Context ) {
Context -> User = UserName;
Context -> ChangeState( new Authenticate() );
}
- SPop can create and store a copy of each state class
When switching
states, just access correct state
- If the state classes (Start, Error,...) have no instance variables
then can use only one object per state class in a program
class Start : public SPopState {
public:
static SPopState* Instance();
void USER ( SPop* Context );
protected:
Start();
private:
static SPopState* _instance;
}
SPopState* Start :: _instance = 0;
SPopState* Start :: Instance() {
if (_instance == 0) _instance = new Start();
return _instance;
}
In SPop, input is read from an external source
Input must be translated from string to commands
if (Input == "USER") x -> USER()
else if (Input == "PASS") x -> PASS()
else if (Input == "LIST") x -> LIST()
Who parses?
- Driver program
- Not recommended, violates information hiding
-
-
- SPop
- Perhaps the logical place
-
-
- SPopState, Start, Error, ...
- If different states have lots of different commands
-
- If you are going to add lots of states
-
- Makes harder for SPop and state classes to interact
Parsing using SPopState and Children
class SPopState {
public:
virtual void input( char* Message, SPop* Context) = 0;
// Common commands
virtual void QUIT ( SPop* Context );
virtual void Error ( SPop* Context );
}
void SPopState :: LIST ( SPop* Context ) {
Context -> ChangeState( new Error() );
}
void SPopState :: QUIT ( SPop* Context ) {
Context -> ChangeState( new Quit() );
}
Start
class Start : public SPopState {
public:
virtual void USER (char* Name, SPop* Context );
virtual void input( char* Message, SPop* Context);
}
void Start :: input( char* Message, SPop* Context) {
if (Message starts with "USER")
USER(last part of Message, Context)
else if (Message starts with "QUIT")
QUIT(Context)
}
void Start :: USER (char* Name, SPop* Context ) {
Context -> User = Name;
Context -> ChangeState( Authenticate::Instance() );
}
Parsing without if statements
Only for the C++ crazies
One does not really want to parse commands of a protocol with if statements
Use a Dictionary (an container that can index items on strings)
// Set up
Dictionary < string, (SPopState ::*) ( *SPop ) > Commands;
Commands["USER"] = SPop :: User( *SPop );
//Usage
string Message
read( Message )
Commands[ first part of message](second part of message)
Driver Program
SPop Protocol;
while ( SPop.state() =! "Quit" ) {
readn( ClientSocket , message ) ;
response = SPop.input ( message );
if ( response =! null ) writen( ClientSocket , response );
}
SPop
SPop Protocol;
Protocol.interactWithClient( ClientSocket );
Problems
- inetd
- IO is via standard in and standard out
-
-
- single-process, concurrent server
- select must drive io
Modules (functions, classes, etc.) should
- do one conceptual thing
-
- be at the same level of detail
-
- hide information
Modules should be created around data not steps in the process
Each module should be characterized by design decisions that it hides from
other modules
Some server code / library issues:
- inetd vs. command line startup
-
- stateless verses stateful protocol
-
- single verses multiple process concurrency
-
- UDP verses TCP
int main(int argc; char* argv[]) {
CommandLineOption ServerOptions(argc, argv);
if ( ServerOptions.containsOneOfFlags( "ip" ) =! TRUE) {
write error message;
exit();
};
if ( ServerOptions.containsFlag( "i") == TRUE ) {// inetd
respondToResquestOn(cin, cout);
}
else {
int PortNumber;
int ConvertError;
ConvertError = stringToInt( ServerOptions.flagValue("p"),
PortNumber);
assert( ConvertError =! -1);
runConcurrentServerOn(PortNumber);
}
}