RCF User Guide


PrevUpHomeNext

Server and client transports are responsible for the actual transmission and reception of network messages. A RcfServer will have one or more server transports, while a client will have a single client transport.

To access the server transports of a RcfServer, you need to capture the return value of RcfServer::addEndpoint():

RCF::RcfServer server;

RCF::ServerTransport & serverTransportTcp = server.addEndpoint( 
    RCF::TcpEndpoint(50001) );

RCF::ServerTransport & serverTransportUdp = server.addEndpoint( 
    RCF::UdpEndpoint(50002) );

server.start();

Alternatively, if the RcfServer only has a single server transport, you can access it by calling RcfServer::getServerTransport():

RCF::RcfServer server( RCF::TcpEndpoint(50001) );
RCF::ServerTransport & serverTransport = server.getServerTransport();

On the client side, the client transport is availablable through ClientStub::getTransport():

RcfClient<I_Echo> client( RCF::TcpEndpoint(50001) );
RCF::ClientTransport & clientTransport = client.getClientStub().getTransport();

For server-side transports, it is generally necessary to set an upper limit on the size of incoming network messages. Without an upper limit, it is possible for malformed requests to cause arbitrarily sized memory allocations on the server.

The maximum incoming message length setting of a RCF server transport defaults to 1 Mb, and can be changed by calling I_ServerTransport::setMaxIncomingMessageLength() :

RCF::RcfServer server( RCF::TcpEndpoint(0) );

// Set max message length to 5 Mb.
server.getServerTransport().setMaxIncomingMessageLength(5*1024*1024);

Similarly, there is a maximum incoming message length setting for client transports. It defaults to 1 Mb and can be changed by calling I_ClientTransport::setMaxIncomingMessageLength() :

RcfClient<I_Echo> client( RCF::TcpEndpoint(123) );

// Set max message length to 5 Mb.
client.getClientStub().getTransport().setMaxIncomingMessageLength(5*1024*1024);

As client transports only receive network messages from a peer they have connected to, the risk of malformed packets is not as great as for server transports.

It is possible to query a RcfClient<> for the sizes of the latest request and response messages sent:

RcfClient<I_Echo> client( RCF::TcpEndpoint(50001) );
client.Echo("1234");

// Retrieve request and response size of the previous call.
RCF::ClientTransport & transport = client.getClientStub().getTransport();

std::size_t requestSize = transport.getLastRequestSize();
std::size_t responseSize = transport.getLastResponseSize();

To set the maximum number of simultaneous connections to a RCF server transport:

RCF::RcfServer server( RCF::TcpEndpoint(0) );

// Allow at most 100 clients to be connected at any time.
server.getServerTransport().setConnectionLimit(100);

This setting is not applicable to the UDP server transport, as the UDP protocol doesn't support a connection concept.

For IP-based server transports, you can allow the local system to assign a port number automatically, by specifying 0 as the port number. When the server starts, the system will find a free port and assign it to the server. The port number can subseqently be retrieved through I_IpServerTransport::getPort():

RCF::RcfServer server( RCF::TcpEndpoint(0) );
server.start();

int port = server.getIpServerTransport().getPort();
RcfClient<I_Echo> client(( RCF::TcpEndpoint(port) ));

For IP-based server transports, client access can be allowed or denied, based on the IP addresses of the clients.

To configure IP rules for allowing clients:

RCF::RcfServer server( RCF::TcpEndpoint(0) );

RCF::IpServerTransport & ipTransport = 
    dynamic_cast<RCF::IpServerTransport &>(server.getServerTransport());

std::vector<RCF::IpRule> rules;

// Match 11.22.33.* (24 significant bits).
rules.push_back( RCF::IpRule( RCF::IpAddress("11.22.33.0"), 24) );

ipTransport.setAllowIps(rules);

server.start();

// Access will be granted to clients connecting from IP addresses matching 11.22.33.* .
// All other clients will be denied.

To configure IP rules for denying clients:

RCF::RcfServer server( RCF::TcpEndpoint(0) );

RCF::IpServerTransport & ipTransport = 
    dynamic_cast<RCF::IpServerTransport &>(server.getServerTransport());

std::vector<RCF::IpRule> rules;

// Match 11.*.*.* (8 significant bits).
rules.push_back( RCF::IpRule( RCF::IpAddress("11.0.0.0"), 8) );

// Match 12.22.*.* (16 significant bits).
rules.push_back( RCF::IpRule( RCF::IpAddress("12.22.0.0"), 16) );

ipTransport.setDenyIps(rules);

server.start();

// Access will be denied to clients connecting from IP addresses matching 11.*.*.*  and 12.22.*.* .
// All other clients will be allowed.

RCF supports both IPv4 and IPv6. To enable IPv6 support in RCF, RCF_USE_IPV6 must be defined (see Building).

For example, to run a server and client over a loopback IPv4 connection:

// Specifying an explicit IPv4 address to bind to.
RCF::RcfServer server( RCF::TcpEndpoint("127.0.0.1", 50001) );
server.start();

// Specifying an explicit IPv4 address to bind to.
RcfClient<I_Echo> client( RCF::TcpEndpoint("127.0.0.1", 50001) );

To run a server and client over a loopback IPv6 connection instead, specify ::1 instead of 127.0.0.1:

// Specifying an explicit IPv6 address to bind to.
RCF::RcfServer server( RCF::TcpEndpoint("::1", 50001) );
server.start();

// Specifying an explicit IPv6 address to bind to.
RcfClient<I_Echo> client( RCF::TcpEndpoint("::1", 50001) );

RCF uses the POSIX getaddrinfo() function to resolve IP addresses. getaddrinfo() can return either IPv4 or IPv6 addresses, depending on the configuration of the local system and network. So the following client will use either IPv4 or IPv6, depending on how the local system and network have been configured:

// Will resolve to either IPv4 or IPv6, depending on what the system 
// resolves machine.domain to.

RcfClient<I_Echo> client( RCF::TcpEndpoint("machine.domain", 50001) );

You can force IPv4 or IPv6 resolution by using the IpAddressV4 and IpAddressV6 classes:

// Force IPv4 address resolution.
RCF::IpAddressV4 addr_4("machine.domain", 50001);
RcfClient<I_Echo> client_4(( RCF::TcpEndpoint(addr_4) ));

// Force IPv6 address resolution.
RCF::IpAddressV6 addr_6("machine.domain", 50001);
RcfClient<I_Echo> client_6(( RCF::TcpEndpoint(addr_6) ));

On machines with dual IPv4/IPv6 stacks, you will probably want your server to listen on both IPv4 and IPv6 addresses. You can do this portably by listening on both 0.0.0.0 and ::0:

// Listen on port 50001, on both IPv4 and IPv6.
RCF::RcfServer server;
server.addEndpoint( RCF::TcpEndpoint("0.0.0.0", 50001) );
server.addEndpoint( RCF::TcpEndpoint("::0", 50001) );
server.start();

On some platforms, it is sufficient to listen only on ::0, as the system will translate incoming IPv4 connections into IPv6 connections with a special class of IPv6 addresses.

When a RcfClient<> connects to a server over an IP-based transport, the default behavior is to allow the system to decide which local network interface and port to use. In some circumstances, you may want to explicitly set the local network interface a client should bind to. This is done by calling I_IpServerTransport::setLocalIp(), before connecting:

RcfClient<I_Echo> client( RCF::TcpEndpoint("127.0.0.1", 50001) );

RCF::IpClientTransport & ipTransport = 
    client.getClientStub().getIpTransport();

// Force client to bind to a particular local network interface (127.0.0.1).
ipTransport.setLocalIp( RCF::IpAddress("127.0.0.1", 0) );

client.getClientStub().connect();

After a RcfClient<> has connected, you can determine which local network interface and port it is bound to, by calling I_IpServerTransport::getAssignedLocalIp():

RcfClient<I_Echo> client( RCF::TcpEndpoint("127.0.0.1", 50001) );

RCF::IpClientTransport & ipTransport = 
    client.getClientStub().getIpTransport();

client.getClientStub().connect();

// Find out which local network interface the client is bound to.
RCF::IpAddress localIp = ipTransport.getAssignedLocalIp();
std::string localInterface = localIp.getIp();
int localPort = localIp.getPort();

RCF provides access to the underlying OS primitives (sockets, handles) of client and server transports. For example:

// Client-side.

RcfClient<I_Echo> client( RCF::TcpEndpoint("127.0.0.1", 50001) );
client.getClientStub().connect();

RCF::TcpClientTransport & tcpClientTransport = 
    dynamic_cast<RCF::TcpClientTransport &>( 
        client.getClientStub().getTransport() );

// Obtain client socket handle.
int sock = tcpClientTransport.getNativeHandle();

// Server-side.

RCF::SessionState & sessionState = RCF::getCurrentRcfSession().getSessionState();

RCF::TcpAsioSessionState & tcpSessionState = 
    dynamic_cast<RCF::TcpAsioSessionState &>(sessionState);

// Obtain server socket handle.
int sock = tcpSessionState.getNativeHandle();

This can be useful if you need to set custom socket options, for instance.

TCP endpoints are represented in RCF by the RCF::TcpEndpoint class, constructed from an IP address and a port number.

RCF::RcfServer server( RCF::TcpEndpoint("0.0.0.0", 50001) );
server.start();

RcfClient<I_Echo> client( RCF::TcpEndpoint("127.0.0.1", 50001) );

Server transports interpret the IP address as the local network interface to listen on. So for example "0.0.0.0" should be specified in order to listen on all available IPv4 network interfaces, and "127.0.0.1" should be specified to listen only on the loopback IPv4 interface. If no IP address is specified, "127.0.0.1" is assumed.

Like RCF::TcpEndpoint, RCF::UdpEndpoint is constructed from an IP address and a port.

RCF::RcfServer server( RCF::UdpEndpoint("0.0.0.0", 50001) );
server.start();

RcfClient<I_Echo> client( RCF::UdpEndpoint("127.0.0.1", 50001) );

However, RCF::UdpEndpoint also contains some extra functionality, to deal with multicasting and broadcasting.

RCF::UdpEndpoint can be configured to listen on a multicast IP address:

// Listen on multicast address 232.5.5.5, on port 50001, on all network interfaces.
RCF::RcfServer server( 
    RCF::UdpEndpoint("0.0.0.0", 50001).listenOnMulticast("232.5.5.5"));

server.start();

Note that the server still needs to specify a local network interface to listen on.

To send multicast messages, specify a multicast IP address and port when creating the client:

RcfClient<I_Echo> client( RCF::UdpEndpoint("232.5.5.5", 50001) );
client.Echo(RCF::Oneway, "ping");

To send broadcast messages, specify a broadcast IP address and port:

RcfClient<I_Echo> client( RCF::UdpEndpoint("255.255.255.255", 50001) );
client.Echo(RCF::Oneway, "ping");

RCF's UDP server transport can be configured to share its address binding, so that multiple RcfServer's can listen on the same port of the same interface. This is enabled by default when listening on multicast addresses, but can also be enabled when listening on non-multicast addresses. This can be useful if multiple processes on the same machine need to listen to the same broadcasts:

EchoImpl echoImpl;

RCF::RcfServer server1( 
    RCF::UdpEndpoint("0.0.0.0", 50001).enableSharedAddressBinding() );

server1.bind<I_Echo>(echoImpl);
server1.start();

RCF::RcfServer server2( 
    RCF::UdpEndpoint("0.0.0.0", 50001).enableSharedAddressBinding());

server2.bind<I_Echo>(echoImpl);
server2.start();

// This broadcast message will be received by both servers.
RcfClient<I_Echo> client( RCF::UdpEndpoint("255.255.255.255", 50001) );
client.Echo(RCF::Oneway, "ping");

In situations where servers are started on dynamically assigned ports, multicasting and broadcasting can be a useful means of communicating server IP addresses and ports to clients. For example:

// Interface for broadcasting port number of a TCP server.
RCF_BEGIN(I_Broadcast, "I_Broadcast")
    RCF_METHOD_V1(void, ServerIsRunningOnPort, int)
RCF_END(I_Broadcast)

// Implementation class for receiving I_Broadcast messages.
class BroadcastImpl
{
public:
    BroadcastImpl() : mPort()
    {}
    void ServerIsRunningOnPort(int port)
    {
        mPort = port;
    }
    int mPort;
};

// A server thread runs this function, to broadcast the server location once 
// per second.
void broadcastThread(int port, const std::string &multicastIp, int multicastPort)
{
    RcfClient<I_Broadcast> client( 
        RCF::UdpEndpoint(multicastIp, multicastPort) );

    client.getClientStub().setRemoteCallSemantics(RCF::Oneway);

    // Broadcast 1 message per second.
    while (true)
    {
        client.ServerIsRunningOnPort(port);
        RCF::sleepMs(1000);
    }
}

// ***** Server side ****

// Start a server on a dynamically assigned port.
EchoImpl echoImpl;
RCF::RcfServer server( RCF::TcpEndpoint(0));
server.bind<I_Echo>(echoImpl);
server.start();

// Retrieve the port number.
int port = server.getIpServerTransport().getPort();        

// Start broadcasting the port number.
RCF::ThreadPtr broadcastThreadPtr( new RCF::Thread( 
    boost::bind(&broadcastThread, port, "232.5.5.5", 50001)));

// ***** Client side ****

// Clients will listen for the broadcasts before doing anything else.
BroadcastImpl broadcastImpl;
RCF::RcfServer clientSideBroadcastListener( 
    RCF::UdpEndpoint("0.0.0.0", 50001).listenOnMulticast("232.5.5.5"));

clientSideBroadcastListener.bind<I_Broadcast>(broadcastImpl);
clientSideBroadcastListener.start();

// Wait for a broadcast message.
while (!broadcastImpl.mPort)
{
    RCF::sleepMs(1000);
}

// Once the clients know the port number, they can connect.
RcfClient<I_Echo> client( RCF::TcpEndpoint(broadcastImpl.mPort));
client.Echo("asdf");

Note that here we are actually using a multicast address to broadcast information to clients. If multicasting had been unavailable on this particular network, we could also have used an IP broadcast address instead of an IP multicast address.

RCF supports Win32 named pipe transports. RCF::Win32NamedPipeEndpoint takes one constructor parameter, which is the name of the named pipe, with or without the leading \\.\pipe\ prefix.

RCF::RcfServer server( RCF::Win32NamedPipeEndpoint("MyPipe") );
server.start();

RcfClient<I_Echo> client( RCF::Win32NamedPipeEndpoint("MyPipe") );

An advantage of using Win32 named pipes, is that they allow easy authentication of clients. A server using a Win32 named pipe server transport can authenticate its clients through the RCF::Win32NamedPipeImpersonator class, which uses the Windows API function ImpersonateNamedPipeClient() to impersonate the client:

RCF_BEGIN(I_Echo, "I_Echo")
    RCF_METHOD_R1(std::string, Echo, const std::string &)
RCF_END(I_Echo)

class EchoImpl
{
public:
    std::string Echo(const std::string & s)
    {
        // Impersonate client.
        RCF::Win32NamedPipeImpersonator impersonator;
        std::cout << "Client user name: " << RCF::getMyUserName();
        return s;
    }
};

If we had used a TCP connection to 127.0.0.1 instead, we would have needed to enable Kerberos or NTLM authentication to securely determine the clients user name (see Transport protocols).

UNIX domain sockets are the UNIX analogue of Win32 named pipes, and allow efficient communication between servers and clients on the same machine. RCF::UnixLocalEndpoint takes one parameter, which is the name of the UNIX domain socket. The name must be a valid filesystem path. For servers, the program must have sufficient privilege to create the given path, and the file must not already exist. For clients, the program must have sufficient privilege to access the given path.

Here is a simple example:

RCF::RcfServer server( RCF::UnixLocalEndpoint("/home/xyz/MySocket"));
server.start();

RcfClient<I_Echo> client( RCF::UnixLocalEndpoint("/home/xyz/MySocket"));


PrevUpHomeNext