Remote Call Framework 3.4
File Transfers

RCF has built-in support for transferring files. While smaller files can be transferred easily in a single remote call, for example by using a RCF::ByteBuffer parameter, this technique fails once the size of the file exceeds the maximum message size of the connection.

To reliably transfer a file of arbitrary size, the file needs to be split into chunks and transmitted in separate calls, with the recipient reassembling the chunks to form the destination file. Furthermore, to attain maximum throughput speeds for large transfers, it is also necessary to run network I/O and disk I/O concurrently.

When you use RCF's built in file transfer functionality, these details are taken care of automatically.

File transfers take place on the same connection as any remote calls you make. So if you enable encryption or compression for a RCF client (see Transport Protocols), the settings will apply to any file transfers performed by that RCF client as well.

To use file transfer functionality, you must define RCF_FEATURE_FILETRANSFER=1 in your build (see Building RCF).

File Downloads

To download a file from a RcfServer, use RCF::ClientStub::downloadFile().

The first argument to RCF::ClientStub::downloadFile() is a download ID, which must be allocated by the RcfServer. Typically the client will make a remote call to the server, and as part of the response to the remote call, a download ID is allocated by your server-side application code, by calling RCF::RcfSession::configureDownload(). The download ID is returned to the client.

The client then uses the download ID to actually download the file.

// Server-side code.
RCF_BEGIN(I_PrintService, "")
RCF_METHOD_V1(void, Print, const std::string&)
RCF_METHOD_R0(std::string, GetDocument)
RCF_END(I_PrintService)
class PrintService
{
public:
void Print(const std::string& msg)
{
std::cout << msg;
}
std::string GetDocument()
{
RCF::Path fileToDownload = "C:\\Document1.pdf";
std::string downloadId = RCF::getCurrentRcfSession().configureDownload(fileToDownload);
return downloadId;
}
};
// Server-side code.
PrintService printService;
server.bind<I_PrintService>(printService);
server.start();
// Client-side code.
RcfClient<I_PrintService> client(RCF::TcpEndpoint(50001));
std::string downloadId = client.GetDocument();
RCF::Path fileToDownloadTo = "C:\\Document1.pdf";
client.getClientStub().downloadFile(downloadId, fileToDownloadTo);

RCF::ClientStub::downloadFile() also takes a third, optional, argument, of type RCF::FileTransferOptions. RCF::FileTransferOptions contains further options for customizing the file transfer, including the ability to download fragments of a file, and assign bandwidth controls to the transfer.

RCF::FileTransferOptions transferOptions;
transferOptions.mBandwidthLimitBps = 1024 * 1024; // 1 MB/sec
client.getClientStub().downloadFile(downloadId, fileToDownloadTo, &transferOptions);

File Uploads

To upload a file to a RcfServer, use RCF::ClientStub::uploadFile().

Uploading files is performed similarly to downloading. The first argument to RCF::ClientStub::uploadFile() is an upload ID that is assigned by the client, and is used in subsequent remote calls by the client to identify the upload.

Once RCF::ClientStub::uploadFile() returns, you can pass the upload ID as a parameter in remote calls, and your server-side application code can then retrieve the uploaded file using the upload ID.

// Server-side code.
RCF_BEGIN(I_PrintService, "")
RCF_METHOD_V1(void, Print, const std::string&)
RCF_METHOD_V1(void, AddDocument, const std::string&)
RCF_END(I_PrintService)
class PrintService
{
public:
void Print(const std::string& msg)
{
std::cout << msg;
}
void AddDocument(const std::string& uploadId)
{
// Application-specific code to process the uploaded file.
// ...
std::filesystem::remove(pathToUpload);
}
};
// Server-side code.
PrintService printService;
server.bind<I_PrintService>(printService);
server.setUploadDirectory("C:\\MyApp\\Uploads");
server.start();
// Client-side code.
RcfClient<I_PrintService> client(RCF::TcpEndpoint(50001));
std::string uploadId = RCF::generateUuid();
RCF::Path fileToUpload = "C:\\Document1.pdf";
client.getClientStub().uploadFile(uploadId, fileToUpload);
client.AddDocument(uploadId);

Just as for downloads, RCF::ClientStub::uploadFile() takes a third, optional, argument of type RCF::FileTransferOptions, with further options for customizing the upload.

RCF::FileTransferOptions transferOptions;
transferOptions.mBandwidthLimitBps = 1024 * 1024; // 1 MB/sec
client.getClientStub().uploadFile(uploadId, fileToUpload, &transferOptions);

Note that on the server-side, RCF::RcfServer::setUploadDirectory() must be called to specify a location for uploaded files. This property must be set before any uploads can take place.

Once a file has been uploaded to a server, it is the responsibility of your server-side application code to manage its lifetime.

Resuming File Transfers

Resuming downloads

If a network disconnection or other problem occurs during a download, RCF::ClientStub::downloadFile() will throw an exception. To resume the download, once the connection is re-established, you can call downloadFile() again, with the same parameters. RCF will resume the download at the point at which it was interrupted.

Resuming uploads

If a network disconnection or other problem occurs during an upload, RCF::ClientStub::uploadFile() will throw an exception. To resume the upload, once the connection is re-established, you can call uploadFile() again, with the same parameters. RCF will resume the upload at the point at which it was interrupted.

Monitoring File Transfers

On the client-side, to monitor a file transfer, use the RCF::FileTransferOptions parameter and set RCF::FileTransferOptions::mProgressCallback to an application-defined callback function. The callback function will be called repeatedly until the file transfer completes.

void clientTransferProgress(const RCF::FileTransferProgress& progress, RCF::RemoteCallAction& action)
{
double percentComplete = (double)progress.mBytesTransferredSoFar / (double)progress.mBytesTotalToTransfer;
std::cout << "Download progress: " << percentComplete << "%" << std::endl;
}
RCF::FileTransferOptions transferOptions;
transferOptions.mProgressCallback = &clientTransferProgress;
client.getClientStub().downloadFile(downloadId, fileToDownloadTo);

On the server-side, you can use RCF::RcfServer::setDownloadProgressCallback() and RCF::RcfServer::setUploadProgressCallback(), to register a callback which will be called every time a chunk is downloaded or uploaded, for any download or upload on that server.

From the callback you can inspect the associated RCF::RcfSession, RCF::FileDownloadInfo and RCF::FileUploadInfo objects. To cancel a file transfer, throw an exception from the callback.

Bandwidth Limits

RCF file transfers will automatically consume as much bandwidth as the network allows. However, in some cases you may want to run file transfers with restricted bandwidth, in order to limit total network bandwidth usage.

Server-Side Bandwidth Limits

You can use RCF::RcfServer::setUploadBandwidthLimit() and RCF::RcfServer::setDownloadBandwidthLimit(), to set a maximum total bandwidth to be consumed by all file transfers to and from a RcfServer.

Here is an example of setting a 1 Mbps upload limit and a 5 Mbps download limit:

RCF::RcfServer server( RCF::TcpEndpoint(50001) );
// 1 Mbps upload limit.
std::uint32_t serverUploadLimitMbps = 1;
// Convert to bytes/second.
std::uint32_t serverUploadLimitBps = serverUploadLimitMbps*1000*1000/8;
server.setUploadBandwidthLimit(serverUploadLimitBps);
// 5 Mbps upload limit.
std::uint32_t serverDownloadLimitMbps = 5;
// Convert to bytes/second.
std::uint32_t serverDownloadLimitBps = serverDownloadLimitMbps*1000*1000/8;
server.setUploadBandwidthLimit(serverDownloadLimitBps);

If for example three clients connect to this server and begin uploading files, the upload bandwidth consumed by all three clients together, will not be allowed to exceed 1 Mbps.

Similarly, if the three clients are downloading files simultaneously, the total download bandwidth consumed by the clients will not be allowed to exceed 5 Mbps.

Custom Server-Side Bandwidth Limits

RCF also supports setting bandwidth limits on a more granular level, with application-defined subsets of clients sharing a particular bandwidth quota.

For example, on a server connected to multiple networks with varying bandwidth characteristics, you would probably want to limit file transfer bandwidth based on which network the file transfer is taking place on. To do this, use RCF::RcfServer::setUploadBandwidthQuotaCallback() and RCF::RcfServer::setDownloadBandwidthQuotaCallback() to configure a callback function for the RcfServer to call, whenever a file transfer is initiated.

From the callback function you can inspect the RCF::RcfSession of the connection, and assign a relevant bandwidth quota to the file transfer.

For example, imagine we want to implement the following bandwidth limits:

  • 1 Mbps upload bandwidth for TCP connections from 192.168.*.*.
  • 56 Kbps upload bandwidth for TCP connections from 15.146.*.*.
  • Unlimited upload bandwidth for all other connections.

To implement this, we need three separate RCF::BandwidthQuota objects, to represent the three bandwidth quotas listed above. Then, we can use RCF::RcfServer::setUploadBandwidthQuotaCallback() to assign bandwidth quotas based on the IP address of the client:

// 1 Mbps quota bucket.
RCF::BandwidthQuotaPtr quota_1_Mbps( new RCF::BandwidthQuota(1*1000*1000/8) );
// 56 Kbps quota bucket.
RCF::BandwidthQuotaPtr quota_56_Kbps( new RCF::BandwidthQuota(56*1000/8) );
// Unlimited quota bucket.
RCF::BandwidthQuotaPtr quota_unlimited( new RCF::BandwidthQuota(0) );
RCF::BandwidthQuotaPtr uploadBandwidthQuotaCb(RCF::RcfSession & session)
{
// Use clients IP address to determine which quota to allocate from.
const RCF::RemoteAddress & clientAddr = session.getClientAddress();
const RCF::IpAddress & clientIpAddr = dynamic_cast<const RCF::IpAddress &>(clientAddr);
if ( clientIpAddr.matches( RCF::IpAddress("192.168.0.0", 16) ) )
{
return quota_1_Mbps;
}
else if ( clientIpAddr.matches( RCF::IpAddress("15.146.0.0", 16) ) )
{
return quota_56_Kbps;
}
else
{
return quota_unlimited;
}
}
// Assign a custom file upload bandwidth limit.
server.setUploadBandwidthQuotaCallback(&uploadBandwidthQuotaCb);

Client-Side Bandwidth Limits

To assign bandwidth limits from the client-side, use RCF::FileTransferOptions::mBandwidthLimitBps and RCF::FileTransferOptions::mBandwidthQuotaPtr.

RCF::FileTransferOptions::mBandwidthLimitBps allows you to configure a bandwidth limit for a single client connection:

RCF::FileTransferOptions transferOptions;
transferOptions.mBandwidthLimitBps = 1024 * 1024; // 1 MB/sec
client.getClientStub().downloadFile(downloadId, fileToDownloadTo, &transferOptions);

RCF::FileTransferOptions::mBandwidthQuotaPtr allows you to share a bandwidth quota between multiple client connections. Here is an example of 3 clients uploading files concurrently, with the total bandwidth used by the clients not allowed to exceed 1 Mb/sec:

RcfClient<I_PrintService> client1(RCF::TcpEndpoint(50001));
RcfClient<I_PrintService> client2(RCF::TcpEndpoint(50001));
RcfClient<I_PrintService> client3(RCF::TcpEndpoint(50001));
RCF::BandwidthQuotaPtr clientQuotaPtr(new RCF::BandwidthQuota(1024*1024));
auto doUpload = [=](RcfClient<I_PrintService>& client)
{
std::string uploadId = RCF::generateUuid();
RCF::Path fileToUpload = "C:\\Document1.pdf";
RCF::FileTransferOptions transferOptions;
transferOptions.mBandwidthQuotaPtr = clientQuotaPtr;
client.getClientStub().uploadFile(uploadId, fileToUpload, &transferOptions);
};
std::vector<std::thread> clientThreads;
clientThreads.push_back(std::thread(std::bind(doUpload, std::ref(client1))));
clientThreads.push_back(std::thread(std::bind(doUpload, std::ref(client2))));
clientThreads.push_back(std::thread(std::bind(doUpload, std::ref(client3))));
for ( std::thread& thread : clientThreads )
{
thread.join();
}