Previous Page
Next Page

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:

  1. The server responds immediately when it connects with an integer representing the highest version of the protocol that the server supports.

  2. The client responds with an integer to indicate the actual version of the protocol that should be used for communication.

  3. The server sends back an 8-byte authentication challenge.

  4. The client sends the authentication challenge back to the server.

  5. 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


Previous Page
Next Page
Converted from CHM to HTML with chm2web Pro 2.85 (unicode)