CEchoEngine
classCEchoEngine
serves three purposes:
It wraps asynchronous connection and shutdown operations in an active object.
It controls the creation and deletion of the active objects that read to and write from a socket (CEchoRead
and CEchoWrite
respectively). This is because these objects are only required from when a connection is a made until the socket is shutdown.
It gathers into a single interface all the functions that users of the engine require to access the engine’s functionality. This means that users do not call functions on CEchoRead
and CEchoWrite
directly. Instead, they use the exported wrapper functions defined by CEchoEngine
.
The public exported functions can be divided to follow these groups, plus construction and destruction:
CEchoEngine
has the usual two-phase construction functions.
IMPORT_C CEchoEngine();
IMPORT_C static CEchoEngine* NewL(MUINotify* aConsole);
IMPORT_C static CEchoEngine* NewLC(MUINotify* aConsole);
IMPORT_C void ConstructL(MUINotify* aConsole);
~CEchoEngine();
Note that the creation functions take a MUINotify
-type argument. This is passed to the CEchoRead
and CEchoWrite
objects when they are created, and so allows all the engine classes to make up-calls to a user interface object.
The ConstructL()
function initialises the objects required to serve the three purposes noted.
EXPORT_C void CEchoEngine::ConstructL(MUINotify* aConsole)
// Construct object, and open a socket
{
iConsole = aConsole;
iEngineStatus = EComplete;
iTimeOut = KTimeOut;
iTimer = CTimeOutTimer::NewL(EPriorityHigh, *this);
CActiveScheduler::Add(this);
// Open channel to Socket Server
User::LeaveIfError(iSocketServ.Connect());
// Open a TCP socket
User::LeaveIfError(iEchoSocket.Open(iSocketServ, KAfInet,
KSockStream, KProtocolInetTcp));
iEchoRead = CEchoRead::NewL(&iEchoSocket, aConsole);
iEchoWrite = CEchoWrite::NewL(&iEchoSocket, aConsole);
}
It takes the following steps:
The engine needs a way to store its current state. iEngineStatus
does this.
We will want to place a limit on the time spent attempting to make a connection. To do this, we define a timer class CTimeOutTimer
, and a CEchoEngine
data member of this type, iTimer
. We can make an asynchronous request to this timer to raise an event after a certain time. If this timer handler is called before the handler for the connection request, we know that this limit has been reached.
The object adds itself to the active scheduler.
A connection to the socket server is made, and a TCP socket initialised. CEchoEngine::iEchoSocket
is an RSocket
data member; CEchoEngine::iSocketServ
is an RSocketServ
data member; KAfInet
indicates the TCP/IP protocol family, while KSockStream
and KProtocolInetTcp
specify TCP. If an error occurs here, it suggests that there is an unrecoverable configuration problem, such as TCP/IP not being installed. We therefore choose to leave on an error.
The active objects that read to and write from a socket are created.
The following functions provide the three type of connections that we want (using an IP address, using a hostname, and using an IP address, obtaining the symbolic name for this IP address first, respectively), and a function to shutdown the current connection.
IMPORT_C void ConnectL(TUint32 aAddr);
IMPORT_C void ConnectL(const TDesC& aServerName);
IMPORT_C void TestGetByAddr(const TInetAddr& aInetAddr);
IMPORT_C void Stop();
The following functions allow users of the engine to read and write data respectively.
IMPORT_C void Read();
IMPORT_C void Write(TChar aChar);
As noted, these functions in turn call the CEchoRead
and CEchoWrite
functions that make the actual read and write calls to RSocket
.
We can now look in more detail at a connection method. Below is the code for the connect by IP address request function.
EXPORT_C void CEchoEngine::ConnectL(TUint32 aAddr)
// Connect to an Echo Socket by IP address
{
iAddress.SetPort(7);
iAddress.SetAddress(aAddr);
iEchoSocket.Connect(iAddress, iStatus);
iEngineStatus = EConnecting;
SetActive();
iTimer->After(iTimeOut);
};
It takes the following steps:
The RSocket::Connect()
function requires a TInetAddr
object holding the destination address and port. The first two lines set up this object (port 7 being the Echo server port, aAddr
being the IP address).
A asynchronous connection call is then made with RSocket::Connect()
.
The engine status is updated appropriately.
SetActive()
is called to inform the active scheduler that a request has been issued.
Finally, we make an asynchronous timer request, to put a limit on the time waited for a connection to be made.
As an active object, CEchoEngine
defines a RunL()
function to be called by the active scheduler when a request completes. The section of this function that handles the connection request just discussed is as follows:
iTimer->Cancel();
switch(iEngineStatus)
{
case EConnecting:
if (iStatus == KErrNone)
{
iConsole->PrintNotify(KConnecting);
iEngineStatus = EConnected;
Read(); //Start CEchoRead Active object
}
else
iConsole->ErrorNotify(KConnectionFailed, iStatus.Int());
break;
The steps are as follows:
As the request has completed, we no longer need the timer, and so it is cancelled.
The switch statement then takes appropriate action based on the engine state variable iEngineStatus
.
If the connection is successful, then we ask the user interface to print a message, update the state, and make the first request to read data from the socket.
In the case of an error, we inform the user interface of this.