Fundamentals of Symbian C++/Sockets
Symbian OS provides a socket framework which supplies a C++ API similar to the BSD C-based socket API. The socket framework supports communication using the Internet protocol suite and also allows for other types of communication, including Bluetooth, USB and IR. The lower layers of the communications ('comms') architecture handle any communication differences so that the sockets API can be used in a transport-independent way. This section describes the framework and discusses the main classes used for sockets programming on Symbian OS.
Symbian also provides the C POSIX sockets API via the Open C/C++ layer.
The Symbian OS socket server (ESOCK.EXE) provides communications between addressable endpoints (sockets) and supports a number of different protocols such as Bluetooth (L2CAP and RFCOMM), USB and IR (IrDA, IrTinyTP and IrMUX).
The socket server process executable is ESOCK.EXE, and its client-side interface is implemented in a DLL called ESOCK.DLL. To use the classes described here, link against esock.lib and #include <es_sock.h>.
The protocols are supplied by plug-in DLLs known as protocol modules (PRTs), which the server loads and unloads as required. One protocol module can contain multiple protocols: for example, the TCPIP.PRT protocol module contains UDP, TCP, ICMP, IP, and DNS, of which UDP and TCP are accessible via sockets to transfer data over IP.
The client-side handle to the socket server is implemented by the RSocketServ class, which is derived from RSessionBase.
RSocketServ provides methods to get information about which protocols are supported. Like the file server, the socket server provides most functions through subsessions: two of the most important subsessions are the RSocket class, representing a socket, and the RHostResolver class, which performs hostname resolution.
RSocketServ is the client-side implementation class which communicates with the socket server, analogous to the RFs client-side file-server session class.
For code to make requests to the socket server, an object of RSocketServ must be instantiated and a session with the socket server established by calling Connect(). The primary function of RSocketServ is to host instances of RSocket and RHostResolver. Any of the RSocket objects which are opened using the session are automatically closed when the session is terminated through a call to RSocketServ::Close(), and cannot be re-used. However, it is recommended to call Close()on each subsession object before closing the session.
RSocketServ can be used to pre-load a PRT by calling the asynchronous function RSocketServ::StartProtocol(). However, client programs do not normally need to call this function, because loading a protocol is managed automatically by the sockets server when a socket of that protocol is opened. However, loading a protocol may be time consuming so a client may prefer to use StartProtocol so as to control when it happens.
RSocketServ::GetProtocolInfo() can be used to acquire a comprehensive description of a protocol’s capabilities and properties, which is returned in a TProtocolDesc object. The number of protocols the socket server is currently aware of can be retrieved by calling RSocketServ::NumProtocols().
RSocketServ is not used to send/receive data or establish connections. The RSocket class implements these functions.
RSocket is the endpoint for all socket-based communications. The class is a subsession of RSocketServ and each object instantiated represents a single socket. The methods of RSocket correspond, for the most part, with the BSD network API functions.
The client socket interface allows:
- socket opening
- active connecting
- data read from and write to a protocol
- passive connections (through the listen/accept model).
This class is used for hostname resolution, providing a generic interface to protocol-specific host-resolution services.
For example, the hostname resolution service used in the Internet is the domain name system (DNS), which translates human-readable domain names to IP addresses. The RHostResolver class provides methods for getting an IP address given a domain name and vice versa. Thus RHostResolver::GetByName() converts the server name to an IP address.
For Bluetooth and infrared, the resolution interface can be used to discover which other devices are in range and available to communicate using those protocols. Queries made through RHostResolver objects are packaged in TNameEntry descriptors which hold TNameRecord objects containing the host name and address.
RHostResolver also provides functions to allow getting and setting the hostname of the local device. However, since the interface is generic, and implementation is provided by each protocol module, not all protocol modules provide all services offered by RHostResolver. Functions return KErrNotSupported if the protocol does not support a given operation. RHostResolver is a subsession of an active socket server session, and a host resolver subsession is opened for a specific protocol by passing an appropriate identifier.
Using Symbian OS Sockets
Connection to the Socket Server
Before an RSocket subsession can be opened, a session with the socket server must be created through a call to RSocketServ::Connect().
The client is responsible for closing a connected socket server session. If the session is a local variable, the client should use the cleanup stack to ensure that it is closed if a leave occurs.
Opening a Socket
Once a server session is established, a socket can be opened by calling one of the overloads of RSocket::Open(), each of which takes the connected socket server session as a parameter. Other parameters that can be specified include:
- The socket address family: KAfInet for IPv4 (the default), KAfInet6 for IPv6. The identifiers to use for other address families can be retrieved from RSocketServ::FindProtocol, given the protocol name.
- The socket type, which may be one of:
- KSockStream: streaming socket
- KSockDatagram: datagram socket
- KSockSeqPacket: datagram socket with sequence numbers
- KSockRaw: raw socket
- The protocol identifier, which among other things may be:
- KProtocolInetTcp for TCP
- KProtocolInetUdp for UDP
- KProtocolInetIp for IPv4
- KProtocolInet6Ip for IPv6
So the code fragment below opens a TCP socket:
User::LeaveIfError(iSocket.Open(iSocketServ, KAfInet, KSockStream, KProtocolInetTcp));
Another overload of RSocket::Open() takes the name of the protocol and uses RSocketServ::FindProtocol() internally to supply sensible values for the parameters.
Again, the client is responsible for closing a connected socket, and if the socket is a local variable the client should use the cleanup stack to ensure that it is closed if a leave occurs.
Configuring and Connecting Sockets
When a socket is opened it has no address associated with it. The local address can be assigned by calling RSocket::Bind().
TInetAddr address(KInetAddrAny, portNumber);
Connected sockets are also configured with the address of the remote socket so that they remain tied together for the duration of the connection.
To retrieve the address of the remote host given its name, use RHostResolver. RHostResolver is initialized in a similar fashion to RSocket and implements GetByName to retrieve a TNameRecord structure, given a host name:
User::LeaveIfError(hostResolver.Open(socketServ, KAfInet, KSockStream, KProtocolInetTcp));
To set up the connection, the client makes a call to the asynchronous RSocket::Connect() method, passing in the address of the remote host.
If a socket has not already been bound to a local address (that is, if Bind() has not yet been called on it), then Connect() will automatically allocate a local address for it.
Connectionless sockets do not need to be configured with the remote address using Connect(), because they can specify the remote address when they actually read or write data. However, it is possible to call Connect() on a connectionless socket: if this is done the socket is bound to a particular remote address and can use the same data transfer calls as for a connected socket. This allows software initially written to use connection-orientated protocols to be quickly ported to use a connectionless one.
Reading from Sockets
Reading from connectionless sockets usually requires the remote address to be specified in the I/O request. Thus, to read from a connectionless socket, one of the overloads of the RSocket::RecvFrom() method are typically used.
Connected sockets use the Read(), Recv() or RecvOneOrMore()in which the remote address does not need to be specified.
RSocket::RecvOneOrMore() completes when any data is available from the connection.
For stream-interfaced sockets such as TCP, RSocket::Recv() will not complete until the entire descriptor (specified by the maximum length of the receive descriptor) is filled with data. This is unlike RecvOneOrMore(), which completes when any amount of data is received. So, unless it is known how much data will be received, Recv() should not be used for TCP or other stream-interfaced protocols.
Recv() and RecvOneOrMore() take four parameters:
- the buffer to receive the data
- a TUint for flags to pass into the protocol (zero if no flags need to be passed in)
- a TRequestStatus: all reading methods are asynchronous
- a reference to an integer which, on completion, contains the number of bytes which were received:.
iSocket.RecvOneOrMore(readBuffer, KDefaultFlags, iStatus, bytesReceived);
Writing to Sockets
Writing to a connectionless socket is performed using one of the overloads of RSocket::SendTo(), which pass the address to which to send the datagram, a buffer of data and flags to control the transfer. Blocking behavior is controlled by passing protocol-specific flags to the call to indicate whether to wait for a receipt.
Writing to connected sockets can use the RSocket::Write() method, which does not need to take address information for the remote endpoint.
RSocket::Write() is a simple asynchronous method which takes a descriptor parameter, passing the entire buffer to the remote socket.
As an alternative, the overloaded RSocket::Send() methods can also be used for connected sockets. The overloads allow for control over the amount of data sent from the buffer and for passing flags to the underlying protocol module to configure the I/O. All implementations provided for reading from and writing to connected sockets are asynchronous, and each has a corresponding cancellation method.
When a socket is closed, the protocol layers involved in the socket connection may also shut down. A socket may have pending connection requests or buffers with data ready to be retrieved. To close the socket gracefully, RSocket::CancelAll() should be called to cancel any outstanding asynchronous operations waiting on data or connections. The socket can then be closed synchronously by calling RSocket::Close() to terminate the socket and cause the operating system to release any resources dedicated to it. This is the typical way to close a connectionless socket and is recommended when there are no pending operations or buffered data waiting.
For connection-oriented protocols, it is usual to disconnect before closing the socket. This can be done by calling RSocket::Shutdown(), which is asynchronous and takes a flag to indicate how to close the socket (for example, to drain the socket by waiting for both output and input buffers to empty, or to stop input but drain the output, or vice versa). Although the Shutdown() method is asynchronous, socket shutdown cannot be cancelled once it has been called.