Wiley.Developing.Software.for.Symbian.OS.2nd.Edition.Dec.2007 (779887), страница 67
Текст из файла (страница 67)
Our RunL() isfirst invoked when the RHostResolver::GetByName() method completes, at which time RunL() calls the next call in the sequence –Connect() – based on the iState value. When Connect() completes, RunL() calls the socket’s Send() method to send the HTTPGET command. When Send() completes, RunL() invokes the socketRecvOneOrMore() method, at which time RunL() is invoked oneach Recv() completion to reissue the RecvOneOrMore() and printthe retrieved web output. When iStatus returns KErrEof (the codeassumes no other call besides RecvOneOrMore() will return this status)the server has finished, so RunL() cleans up and no further commandsare reissued.HandleError() would close all the network objects and coulddisplay the error string, such as below:void CWebPage::HandleError(TDesC& aErrMsg){iSocket.Close();iResolver.Close();iSocketServ.Close();PrintError(aErrMsg); // some generic message display routine}The error handling as well as the completion handling in this exampleis not ideal, since typically you would want the caller of OutputWebPage() to have control over error handling and know when the webpage retrieval is complete.
The next example will provide the ability tonotify the caller of completion and errors so it can handle itself.11.4 Example: Retrieving Weather InformationThis section presents an example program using the Symbian OSsocket API to retrieve the current temperature from the weather server346SYMBIAN OS TCP/IP NETWORK PROGRAMMINGhttp://www.wunderground.com. The example consists of an activeobject, which steps through the various socket calls needed to collect thedata from the server.
The active object provides a method called GetTemperature(const TDesC& aCity) – where aCity is a descriptorthat contains the airport code for the US city whose temperature youwant. When this function is called, the data is collected from the weatherserver and parsed. Then an info message is displayed on the screen thatshows the information in the form of Temperature=XX, where XX is thelast reported temperature for the specified city.11.4.1 wunderground.comhttp://www.wunderground.com is a website that provides weather information. In addition to its HTTP website, wunderground also provides atelnet interface (through rainmaker.wunderground.com, port 3000).Using the telnet interface, you can enter a three-letter US city code, andretrieve the current and forecast weather conditions for that city in a simple text format.
Since this text is easier to parse than HTML, the examplehere uses the telnet interface.First, let’s run the telnet manually, so we can see what this serveroutputs. Figure 11.3 shows the output when the following is typed at acommand prompt:telnet rainmaker.wunderground.com 3000and then AUS (for Austin, TX) is typed in answer to the city code prompt.Notice that, in Figure 11.3, the current temperature follows the endof the line filled with ‘=’ characters. I will use this fact to retrieve thetemperature in the example code.The following shows the active object class definition for the example:#include <e32base.h>#include <in_sock.h>class CWeatherInfo : public CActive{public:static CWeatherInfo* NewL(MWeatherObserver& aObserver);~CWeatherInfo();void GetTemperature(const TDesC& aCity);protected:CWeatherInfo(MWeatherObserver& aObserver);void RunL();void DoCancel();enum TLoadState{EInitializing,EResolvingName,EConnecting,ESending,EXAMPLE: RETRIEVING WEATHER INFORMATION347EReceiving};private:TLoadState iCommState;RSocketServ iSocketSrv;RSocket iSocket;TNameEntry iNameEntry;RHostResolver iResolver;TBuf8<20000> iNetBuff;TSockXfrLength iLen;TBuf<16> iCityCode;MWeatherObserver& iObserver;void Cleanup(TInt aError);};Following is the code for the NewL(), constructor, destructor, andDoCancel() functions:Figure 11.3Output of wunderground telnet session348SYMBIAN OS TCP/IP NETWORK PROGRAMMINGCWeatherInfo* CWeatherInfo::NewL(MWeatherObserver& aObserver){CWeatherInfo* self = new(ELeave) CWeatherInfo(aObserver);CActiveScheduler::Add(self);return self;}CWeatherInfo::CWeatherInfo(MWeatherObserver& aObserver): CActive(CActive::EPriorityStandard), iObserver(aObserver){}CWeatherInfo::~CWeatherInfo(){// Make sure we’re cancelledCancel();}void CWeatherInfo::DoCancel(){iSocket.CancelAll();}NewL() is a static function that creates the CWeatherInfo activeobject, adds it to the current active scheduler, and returns a pointer tothe created instance.
An object with an interface of type MWeatherObserver is passed to NewL(). I defined MWeatherObserver as follows(putting in the same header file as CWeatherInfo):class MWeatherObserver{public:virtual void TemperatureReport(TDesC& aCity, TDesC& aTemperature)=0;virtual void TemperatureError(TDesC& aErrStr, TInt aErrCode)=0;};The parameter object should implement TemperatureReport()and TemperatureError(), which act as callbacks to handle thecompletion of the retrieval of the temperature started via GetTemperature(). CWeatherInfo will call TemperatureReport() uponsuccessfully obtaining the temperature of the request city.
If an erroroccurs, TemperatureError() is called, passing it an error string aswell as a code.Recall from Chapter 4 that M classes are for adding a particular interface to an object. Interfaces are usually added to objects via multipleinheritance. The class that derives from the interface class must implement the virtual functions in the interface (they are defined as pure virtualin the interface class).
For example, when I modified the S60 SimpleEx to use CWeatherInfo, I inherited the SimpleEx UI class fromMWeatherObserver and implemented TemperatureReport() andTemperatureError() in the UI class – we’ll look at that code shortly(in UIQ I inherited the view class from MWeatherObserver instead ofthe UI class).EXAMPLE: RETRIEVING WEATHER INFORMATION349The CWeatherInfo constructor passes the active object priorityto the base constructor and sets the passed MWeatherObserver classreference to the member variable iObserver for later use.
The destructorcalls DoCancel(), which cancels any asynchronous call in progress sothat the active object can safely be destroyed.The following code shows the implementation of GetTemperature(). You call this function to start the process of collecting theweather information from which the temperature will be extracted anddisplayed.void CWeatherInfo::GetTemperature(const TDesC& aCity){// if we are already getting the temperature, then return.if (IsActive())return;iCommState=EInitializing;TInt res =iSocketSrv.Connect();if (res != KErrNone){Cleanup(res);return;}iCityCode.Copy(aCity);res = iSocket.Open(iSocketSrv,KAfInet,KSockStream,KProtocolInetTcp);if (res != KErrNone){Cleanup(res);return;}// Resolve name, rest handled by RunL()res = iResolver.Open(iSocketSrv, KAfInet, KProtocolInetTcp);if (res != KErrNone){Cleanup(res);return;}iCommState=EResolvingName;_LIT(KWeatherServerName,"rainmaker.wunderground.com");iResolver.GetByName(KWeatherServerName, iNameEntry, iStatus);SetActive();}GetTemperature() first checks if the active object is currentlyactive and exits if it is.
This prevents a panic E32USER-CBase 42 (setting an active object to the active state while it is already active), whichcould happen if GetTemperature() was called while a temperatureretrieval is already in progress. If the active object is not active, GetTemperature() copies the city code, which has been passed to it,to a member variable for sending to the server later, and then opens aTCP socket. It creates an RHostResolver so that it can do the firststep in the sequence – getting the weather server’s IP address. The active350SYMBIAN OS TCP/IP NETWORK PROGRAMMINGobject maintains the sequence state in iCommState, and the first stateis EResolvingName. The RHostResolver’s GetByName() is calledto begin the DNS name lookup, and then SetActive() is called.Recall that SetActive() is a method that returns immediately, butsets a flag indicating to the active scheduler that this active object isnow expecting an asynchronous function event.
The result is that theactive object’s RunL() function will be called when the GetByName()function completes (either by getting the name, or following an error).The RHostResolver::GetByName() lookup of rainmaker.wunderground.com was done as an illustration. However, you couldhave looked up the IP address for rainmaker.wunderground.com(it’s 66.28.69.161) manually upfront using a program like ping from aPC command line – then hardcoded this address and passed it to thesocket Connect() method, thus skipping the DNS lookup and makingthe program faster.The call to GetTemperature() returns after initiating GetByName(). The remaining sequence of socket calls used to retrieve theweather information is performed in response to asynchronous eventshandled in the active object’s RunL() – the first event being the GetByName() completion event.The active object’s RunL() function is as follows:void CWeatherInfo::RunL(){if (iStatus != KErrNone){Cleanup(iStatus.Int());}else{switch(iCommState){case EResolvingName:{TInetAddr destAddr;destAddr=iNameEntry().iAddr;destAddr.SetPort(3000);// Connect to the remote hostiCommState=EConnecting;iSocket.Connect(destAddr,iStatus);SetActive();break;}case EConnecting:{_LIT(KCRLF,"\xD\xA");TBuf8<300> getBuff;getBuff.Copy(KCRLF);getBuff.Append(iCityCode);getBuff.Append(KCRLF);iCommState=ESending;iSocket.Send(getBuff,0,iStatus);EXAMPLE: RETRIEVING WEATHER INFORMATION351SetActive();break;}case ESending:{// Start receivingiCommState=EReceiving;iSocket.RecvOneOrMore(iNetBuff,0,iStatus,iLen);SetActive();break;}case EReceiving:{/*------------------------------------------------------------------The rainmaker.wunderground.com line with the temperature startsafter a line filled with '='s.-------------------------------------------------------------------*/_LIT8(KFileTok,"=\xA");TInt pos = iNetBuff.FindF(KFileTok);TBuf<100> temp;if (pos != KErrNotFound){temp.Copy(iNetBuff.Mid(pos+2,10));temp.Trim();iObserver.TemperatureReport(iCityCode,temp);Cleanup(KErrNone);} else{iSocket.RecvOneOrMore(iNetBuff,0,iStatus,iLen);SetActive();}break;}}}}RunL() is invoked on completion of each socket call, and thesequence of network operations needed to collect the temperaturefrom wunderground.com is accomplished through a state machine,illustrated in Figure 11.4.The first event processed by RunL() is the resolution of the host nameto an IP address.