Recipe 24.4.
Handshaking with a Socket Server
Problem
You want to do handshaking with a socket server and
need to know what the received data's context is to know how to
process it.
Solution
Create different constant variables to represent
states of the protocol. Use the constants to map particular
processing functions with the corresponding state. In a
socketData event handler, call the appropriate function by
invoking it through the state map.
Discussion
A common scenario when connecting to a socket is
going through a handshake process. Typically, the server initially
sends data to the client. The client then responds to the data in a
particular manner, and the server responds again accordingly. This
entire process repeats until the handshaking is complete and a
"normal" connection is established.
It gets difficult to process the response from
the server because the socketData event handler does
not keep track of context. That is, there is no "why" sent along
with the server response, or no "this data is in response to"
processing directive. Knowing how to process the response from the
server is not usually something that can be gathered through the
response itself, especially when the response varies. Perhaps one
response returns two bytes and another returns an integer followed
by a double. You can begin to see how this presents itself as a
problem.
The solution is to create various state
constants to represent the different contexts in which the server
sends data to the client. By associating each of these constants
with a particular function to handle the data, you can easily call
the correct processing function based on the current state of the
protocol.
Consider the following handshaking scenario that
happens when you connect to a socket server:
-
The server responds immediately when it connects
with an integer representing the highest version of the protocol
that the server supports.
-
The client responds with an integer to indicate
the actual version of the protocol that should be used for
communication.
-
The server sends back an 8-byte authentication
challenge.
-
The client sends the authentication challenge
back to the server.
-
The server closes the connection if the client
response was not what the server was expecting, or, at this point,
the protocol moves into a normal operating mode and the handshaking
is complete.
In reality, Step 4 involves a more secure
response to an authentication challenge. Instead of just sending
back the challenge verbatim, you would really want to use some sort
of encryption with a user-supplied key. Perhaps the client asks the
user for a password, and then the password entered is used as the
encryption key for the 8-byte challenge. The encrypted challenge is
then sent back to the server. If this challenge response matches
what the server expected, then the client knew the right password
and the connection should be allowed.
To implement the handshaking processed outlined,
you first want to create constants to represent the different kinds
of data returned from the server. First, there is determining the
version from Step 1. Second, there is receiving the challenge from
Step 3. Finally, there is the normal operating mode from Step 5.
These can be represented by the following constants:
public const DETERMINE_VERSION:int = 0;
public const RECEIVE_CHALLENGE:int = 1;
public const NORMAL:int = 2;
It doesn't matter what values are given to the
constants. Rather, the only important part is that all the values
are different so no two constants represent the same int
value.
The next step is to create different functions
to process the data. The three functions created will be named
readVersion( ),
readChallenge( ), and
readNormalProtocol( ).
After the functions have been defined, a map must be created to
associate one of the previous state constants with the function
used to process the data received during that state. The code for
that looks like this:
stateMap = new Object( );
stateMap[ DETERMINE_VERSION ] = readVersion;
stateMap[ RECEIVE_CHALLENGE ] = readChallenge;
stateMap[ NORMAL ] = readNormalProtocol;
The final step is to code the
socketData event handler in such a way that the correct
processing function is invoked based on the current state of the
protocol. To do this, a currentState int variable
is created. Then, using the stateMap, the processing
function is invoked by looking up the function associated with
currentState:
var processFunc:Function = stateMap[ currentState ];
processFunc( ); // Invoke the appropriate processing function
There is a little bit of bookkeeping involved in
this process. Be sure to update currentState in your code
to accurately reflect the current state of the protocol.
The entire code example to process the
handshaking scenario previously described looks like the
following:
package {
import flash.display.Sprite;
import flash.events.ProgressEvent;
import flash.net.Socket;
import flash.utils.ByteArray;
public class SocketExample extends Sprite {
// The state constants to describe the protocol
public const DETERMINE_VERSION:int = 0;
public const RECEIVE_CHALLENGE:int = 1;
public const NORMAL:int = 2;
// Maps a state to a processing function
private var stateMap:Object;
// Keeps track of the current protocol state
private var currentState:int;
private var socket:Socket;
public function SocketExample( ) {
// Initialzes the states map
stateMap = new Object( );
stateMap[ DETERMINE_VERSION ] = readVersion;
stateMap[ RECEIVE_CHALLENGE ] = readChallenge;
stateMap[ NORMAL ] = readNormalProtocol;
// Initialze the current state
currentState = DETERMINE_VERSION;
// Create and connect the socket
socket = new Socket( );
socket.addEventListener( ProgressEvent.SOCKET_DATA, onSocketData );
socket.connect( "localhost", 2900 );
}
private function onSocketData( event:ProgressEvent ):void {
// Look up the processing function based on the current state
var processFunc:Function = stateMap[ currentState ];
processFunc( );
}
private function readVersion( ):void {
// Step 1 - read the version from the server
var version:int = socket.readInt( );
// Once the version is read, the next state is receiving
// the challenge from the server
currentState = RECEIVE_CHALLENGE;
// Step 2 - write the version back to the server
socket.writeInt( version );
socket.flush( );
}
private function readChallenge( ):void {
// Step 3 - read the 8 byte challenge into a byte array
var bytes:ByteArray = new ByteArray( );
socket.readBytes( bytes, 0, 8 );
// After the challenge is received, the next state is
// the normal protocol operation
currentState = NORMAL;
// Step 4 - write the bytes back to the server
socket.writeBytes( bytes );
socket.flush( );
}
private function readNormalProtocol( ):void {
// Step 5 - process the normal socket messages here now that
// that handshaking process is complete
}
}
}
See Also
Recipes 18.2,
24.1,
and 24.3
|