HTTP Example Using Symbian OS Socket API

Now let's look at the OutputWebPage() program from section 11.2.3, rewritten to use the Symbian OS socket API. The first thing to note is that many of the socket functions are asynchronous functions, and I use User::WaitForRequest() to wait for them to complete. This is for simplicity in showing the API; however, these functions are most effectively used in active objects (see section 11.3.3).

Note that Symbian OS does provide an HTTP framework API, and using this framework makes the most sense when implementing code to retrieve web pages (I will not cover this framework in this book). However, I will use the general Symbian OS network APIs to implement the HTTP examples here for the purposes of demonstrating general network programming in Symbian OS.

void HandleError(TDesC& aMsg, RSocketServ& aSockServ)

aSockServ.Close();

PrintError(aMsg);

TInt OutputWebPage(const TDesC& aServerName, const TDesC& aDoc)

RSocketServ sockSrv;

TInt res = sockSrv.Connect();

_LIT(KSockOpenFail,"Error connecting to socket server"); HandleError(KSockOpenFail,sockSrv);

RSocket sock;

res = sock.Open(sockSrv,KAfInet,KSockStream, KProtocollnetTcp);

_LIT(KSockOpenFail,"Socket open failed"); HandleError(KSockOpenFail,SockServ);

TNameEntry nameEntry; RHostResolver resolver;

res = resolver.Open(sockSrv, KAflnet, KProtocollnetTcp);

_LIT(KResvOpenFail,"host resolver open failed"); HandleError(KResvOpenFail, SockServ);

TRequestStatus status;

resolver.GetByName(aServerName, nameEntry, status); // wait for completion of asynchronous function. In real code, // much better to use active objects for completions instead of these // calls.

User::WaitForRequest(status); resolver.Close();

_LIT(KDnsFail,"DNS lookup failed"); HandleError(KDnsFail,SockServ);

TInetAddr destAddr;

destAddr = nameEntry().iAddr; // set address to DNS-returned IP

// address destAddr.SetPort(80); // Set to well-known HTTP port // Connect to the remote host sock.Connect(destAddr,status); User::WaitForRequest(status);

_LIT(KSocketConnectFail,"Failed to connect to server"); HandleError(KSocketConnectFail,SockServ);

// Assemble HTTP GET command

TBuf8<30 0> getBuff;

_LIT8(KGetCommand,"GET");

getBuff.Copy(KGetCommand);

getBuff.Append(aDoc);

getBuff.Append(KCRLF);

sock.Send(getBuff,0,status);

User::WaitForRequest(status);

{

TSockXfrLength len;

sock.RecvOneOrMore

(buff,0,status,len);

User::WaitForRequest(status);

PrintOutput(buff);

// some generic 8-bit output-to screen or file

} while (status ==

KErrNone);

sock.Close();

sockSrv.Close();

return (KErrNone); }

Connecting to the socket server

Before using the socket API, you must first establish a session with the socket server. Note that when I use the term 'socket server' here, I mean the client-server process in Symbian OS that handles sockets, and not a destination server machine. To establish a session with the socket server, you instantiate an RSocketServ object and call its Connect() function, as shown below:

RSocketSrv sockSrv;

TInt res = sockSrv.Connect();

_LIT(KSockOpenFail,"Error connecting to socket server"); HandleError(KSockOpenFail,sockSrv);

Socket handling, like many other functions in Symbian OS, is best performed by means of a server process, along with client-side interface classes to access the server's services. The client-side classes for the socket server comprise the socket API.

The socket server handles all the details of creating sockets, connecting them to the client and server, and communicating through sockets in a transparent fashion. At this level of network programming, you don't need to know the details of the Symbian OS network communication architecture but, if you are interested, they are covered in greater depth in Chapter 3 (see section 3.10), and in a forthcoming book from Symbian Press.3

Creating the socket

To create and open a socket, you instantiate an RSocket class and call its Open() method. This is done in the example as:

TInt res = sock.Open(sockSrv,KAfInet,KSockStream, KProtocolInetTcp);

3 Symbian OS Communications Programming, John Wiley & Sons, 2007, ISBN 0470512288.

Open() has the following form:

TInt Open(RSocketServ& aServ,TUint aAddrFamily, TUint aSocketType, TUint aProtocol);

The first argument is the connected RSocketServ class - this is needed because each RSocket is a subsession of the client socket server session established by RSocketServ.

The last three arguments are similar to those for the C socket() call. KAfInet specifies the TCP/IP v4 protocol suite. socketType is set to KSockStream for TCP, or KSockDatagram for UDP. protocol should be:

• KProtocolInetTcp for TCP

• KProtocolInetUdp for UDP.

Unlike in the BSD socket API, protocol cannot be zero.

Setting the destination address

The class TInetAddr represents an endpoint's IP address and port, which can be set up using SetAddress() and SetPort(), respectively. For example, the following code sets up a TInetAddr to represent IP address 10.1.2.3, port 80:

TInetAddr addr;

addr.SetAddress(INET_ADDR(10,1,2,3)); addr.SetPort(80);

inet_addr is a macro that writes the quad address into a 32-bit value that contains the four address bytes.

In our HTTP example, we are passed the web server name, so we need to use the RHostResolver class to contact DNS and look up the IP address associated with that name. To use the RHostResolver, you open it for the appropriate protocol (in this case TCP) and then call RHostResolver's GetByName() method to look up the corresponding IP address.

In the example, this is accomplished by:

TNameEntry nameEntry; RHostResolver resolver;

res = resolver.Open(sockSrv, KAfInet, KProtocolInetTcp);

_LIT(KResvOpenFail,"host resolver open failed"); HandleError(KResvOpenFail, SockServ);

TRequestStatus status;

resolver.GetByName(aServerName, nameEntry, status);

User::WaitForRequest(status);

resolver.Close();

_LIT(KDnsFail,"DNS lookup failed"); HandleError(KDnsFail,SockServ);

As mentioned previously (see section 11.3.1), GetByName() converts the server name in aServerName to an IP address. The first argument of GetByName() is the server name you want translated. The results of the lookup are put in nameEntry upon return. When you assign nameEn-try().iAddr to a TInetAddr, you'll set the IP address associated with the server name, then you just need to set the port (port 80 in our case for HTTP).

You can now set up the destination address that you will use to connect to the server:

TInetAddr destAddr;

destAddr = nameEntry().iAddr;

//

Set

address

to DNS returned IP address

destAddr.SetPort(80);

//

Set

to well-

■known HTTP port

Connection to the remote server

The RSocket Connect() is used to establish a connection with the remote web server:

sock.Connect(destAddr,status); User::WaitForRequest(status);

_LIT(KSocketConnectFail,"Failed to connect to server"); HandleError(KSocketConnectFail,SockServ);

Sending a packet

Once the socket is created and connected, packets can be sent (remember, a connection is not required in the case of UDP). RSocket provides the Send() method to send data through the socket. Send() has the following form:

void Send(const TDesC8& aBuffer, TUint aFlags, TRequestStatus& aStatus)

The buffer to send is specified as an 8-bit descriptor, and all bytes in the descriptor are sent through the socket.

The HTTP GET command in our example is sent to the web server as follows:

TBuf8<300> getBuff;

getBuff.Append(aDoc);

sock.Send(getBuff,0,status);

User::WaitForRequest(status);

RSocket also has a SendTo() method that is used to send UDP data. This method has the same form as Send(), except an extra argument is added to specify the remote address that the packet should go to. The following code shows a way of sending data via UDP using SendTo(). Note that I have omitted error checking for simplicity.

RSocket sock; RSocketSrv sockSrv; sockSrv.Connect();

sock.Open(socksvr,KafInet,KSockDatagram,0); TInetAddr destAddr;

destAddr.SetAddr(INET_ADDR(10,1,2,3);

destAddr.SetPort(80);

TBuf8<300> buff;

buff.Copy(_L("Some stuff to send over UDP"));

sock,SendTo(buff,destAddr,0,iStatus);

User::WaitForRequest(iStatus);

You can also use Send() to send UDP data, provided you first call Connect(), to connect to the remote address. Connect() is a convenience method for UDP, you call it so that the remote address need not be specified every time you send a UDP packet.

Receiving packets

The web page is retrieved in our HTTP example as follows:

TBuf8<200> buff;

TSockXfrLength len;

sock.RecvOneOrMore(buff,0,status,len); User::WaitForRequest(status);

PrintOutput(buff); // some generic 8-bit output-to screen or file } while (status == KErrNone);

The example uses the RSocket::RecvOneOrMore() method to retrieve the data sent from the server. RecvOneOrMore() has the following form:

void RecvOneOrMore(TDes8& aDesc, TUint aFlags, TRequestStatus& aStatus, TSockXfrLength& aLen)

RecvOneOrMore() acts like the BSD recv() socket call in that it completes when anydata is available from the connection. The receive buffer (aDesc) is specified as an 8-bit descriptor and the received data is added to this buffer. The size of the descriptor is updated to match the number of bytes received (aLen also returns the number of bytes that were received).

There is another RSocket method to receive data, called Recv(). You may be tempted to use Recv() instead of RecvOneOrMore() due to the name matching the BSD recv() call. However, there is a big difference between these receive calls when using TCP. Unlike RecvOneOrMore(), which completes when any amount of data is received, Recv() will not complete until the entire descriptor (specified by the maximum length of the receive descriptor) is filled with data. So, unless you know exactly how many bytes you will receive from the server, do not use Recv() for TCP.

Recv() is usually used for UDP. It behaves differently for UDP in that Recv() returns the data from a received UDP datagram even if it is below the maximum length of the descriptor (bear in mind that the Recv() method can only be used for UDP if Connect() was called first). So, Recv() acts the same for UDP as RecvOneOrMore() does for TCP.

RSocket also provides a method called RecvFrom() for receiving UDP data. This method is equivalent to the BSD recvfrom() function. It receives a UDP packet and also the address of the host that sent it. RecvFrom() has the following form:

void RecvFrom(TDes8& aDesc, TSockAddr& anAddr, TUint aFlags, TRequestStatus& aStatus)

This function receives UDP data and supplies not only the data received but also the address of the endpoint that sent the data. TSockAddr is the base class for TInetAddr, so a TInetAddr can be passed here to obtain the sending node's address.

Closing the socket and socket server

The example cleans up the socket and socket server connection with:

sock.Close(); sockSrv.Close();

0 0

Post a comment

  • Receive news updates via email from this site