RCF has built-in support for transferring files and directories. While smaller
files can be transferred easily in a single call (for example using RCF::ByteBuffer
arguments), this technique fails once the size of the file exceeds the maximum
message size of the connection.
To reliably transfer files of arbitrary size, the files need to be split into
chunks and transmitted in separate calls, with the recipient reassembling the
chunks to form the destination file. RCF automates this chunking logic in a
secure and efficient way, using the RCF::FileUpload
and RCF::FileDownload
classes.
RCF clients can upload files or directories to a server by including a FileUpload
object as a remote call parameter:
RCF_BEGIN(I_HelloWorld, "I_HelloWorld") RCF_METHOD_V1(void, Print, const std::string &) RCF_METHOD_V1(void, UploadFiles, RCF::FileUpload) RCF_END(I_HelloWorld)
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) ); RCF::FileUpload fileUpload("C:\\FileToUpload.zip"); client.UploadFiles(fileUpload);
The serialization implementation of the FileUpload
class takes care of transferring the nominated files to the server. On the
server-side, the servant object that the remote call is dispatched to, uses
the FileUpload
object to
locate the newly uploaded files on the local system:
class HelloWorldImpl { public: void Print(const std::string & s) { std::cout << "I_HelloWorld service: " << s << std::endl; } void UploadFiles(RCF::FileUpload fileUpload) { // Where are the uploaded files. std::string uploadPath = fileUpload.getLocalPath(); // Which files have been uploaded. RCF::FileManifest & manifest = fileUpload.getManifest(); } };
If the connection to the server is lost during the transfer, the upload can
be resumed by reusing the same FileUpload
object:
// Call UploadFiles with the same arguments again, to resume an interrupted upload. client.UploadFiles(fileUpload);
To monitor the progress of the upload, use ClientStub::setFileProgressCallback()
:
void onFileTransferProgress(const RCF::FileTransferProgress & progress) { std::cout << "Total bytes to transfer: " << progress.mBytesTotalToTransfer << std::endl; std::cout << "Bytes transferred so far: " << progress.mBytesTransferredSoFar << std::endl; std::cout << "Server-side bandwidth limit (if any): " << progress.mServerLimitBps << std::endl; }
client.getClientStub().setFileProgressCallback(&onFileTransferProgress);
To encrypt the file upload, set the transport protocol of the RcfClient<>
object, before commencing the upload. For instance, to use NTLM:
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);
On the server, the default upload location is a subdirectory of the current
working directory. You can use RcfServer::setFileUploadDirectory()
to set a different location for file uploads:
server.setFileUploadDirectory("C:\\RCF-Uploads");
Once files are uploaded to the server, the lifetime of the uploaded files becomes the responsibility of the server application.
The server can also monitor the progress of all file uploads, as well as cancel file uploads:
void onServerFileUpload(RCF::RcfSession & session, const RCF::FileUploadInfo & uploadInfo) { // Which files are being uploaded. const RCF::FileManifest& manifest = uploadInfo.mManifest; // How far has the upload progressed. boost::uint32_t currentFile = uploadInfo.mCurrentFile; boost::uint64_t currentFilePos = uploadInfo.mCurrentPos; // To cancel the upload, throw an exception. // ... }
server.setOnFileUploadProgress(&onServerFileUpload);
File downloads are implemented in RCF in a similary way to file uploads.
RCF clients can download files or directories from a server by including
a FileDownload
object as
a remote call parameter:
RCF_BEGIN(I_HelloWorld, "I_HelloWorld") RCF_METHOD_V1(void, Print, const std::string &) RCF_METHOD_V1(void, DownloadFiles, RCF::FileDownload) RCF_END(I_HelloWorld)
RcfClient<I_HelloWorld> client( RCF::TcpEndpoint(50001) ); RCF::FileDownload fileDownload; client.DownloadFiles(fileDownload); std::string localFilePath = fileDownload.getLocalPath(); RCF::FileManifest & fileManifest = fileDownload.getManifest();
The servant object that the remote call is dispatched to, uses the FileDownload
object to set the files that
will be downloaded to the client:
class HelloWorldImpl { public: void Print(const std::string & s) { std::cout << "I_HelloWorld service: " << s << std::endl; } void DownloadFiles(RCF::FileDownload fileDownload) { fileDownload = RCF::FileDownload("C:\\FileToDownload.zip"); } };
Interrupted downloads can be resumed by specifying the same download location as for the previous download attempt:
// Either reuse the FileDownload object from the previous call: client.DownloadFiles(fileDownload); // , or call FileDownload::setDownloadToPath() on a new FileDownload: RCF::FileDownload secondFileDownload; std::string previousDownloadToPath = fileDownload.getDownloadToPath(); secondFileDownload.setDownloadToPath(previousDownloadToPath); client.DownloadFiles(secondFileDownload);
To monitor the progess of a file download, use ClientStub::setFileProgressCallback()
:
void onFileTransferProgress(const RCF::FileTransferProgress & progress) { std::cout << "Total bytes to transfer: " << progress.mBytesTotalToTransfer << std::endl; std::cout << "Bytes transferred so far: " << progress.mBytesTransferredSoFar << std::endl; std::cout << "Server-side bandwidth limit (if any): " << progress.mServerLimitBps << std::endl; }
client.getClientStub().setFileProgressCallback(&onFileTransferProgress);
To encrypt the download, set an appropriate transport protocol on the RcfClient<>
object:
client.getClientStub().setTransportProtocol(RCF::Tp_Ntlm);
Similarly to file uploads, the server can monitor and optionally cancel any file downloads that are in progress:
void onServerFileDownload(RCF::RcfSession & session, const RCF::FileDownloadInfo & uploadInfo) { // Which files are being downloaded. const RCF::FileManifest& manifest = uploadInfo.mManifest; // How far has the download progressed. boost::uint32_t currentFile = uploadInfo.mCurrentFile; boost::uint64_t currentFilePos = uploadInfo.mCurrentPos; // To cancel the download, throw an exception. // ... }
server.setOnFileDownloadProgress(&onServerFileDownload);
RCF file transfers will automatically run as fast as the network allows them to run. However, in some cases you may want to limit bandwidth usage and run file transfers at a restricted pace. RCF allows you set the maximum bandwidth consumed by all file uploads, or all file downloads, on a server:
RCF::RcfServer server( RCF::TcpEndpoint(50001) ); // 1 Mbps upload limit. boost::uint32_t serverUploadLimitMbps = 1; // Convert to bytes/second. boost::uint32_t serverUploadLimitBps = serverUploadLimitMbps*1000*1000/8; server.setFileUploadBandwidthLimit(serverUploadLimitBps); // 5 Mbps upload limit. boost::uint32_t serverDownloadLimitMbps = 5; // Convert to bytes/second. boost::uint32_t serverDownloadLimitBps = serverDownloadLimitMbps*1000*1000/8; server.setFileUploadBandwidthLimit(serverDownloadLimitBps);
In this example, if three clients are uploading files simultaneously, the total bandwidth consumed by all three clients will not be allowed to exceed 1 Mbps. Similarly, if there are three clients downloading files simultaneously, the total bandwidth consumed by the downloads will not be allowed to exceed 5 Mbps.
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 may want to limit file transfer bandwidth based on which
network the file transfer is taking place on. To do this, use RcfServer::setFileUploadCustomBandwidthLimit()
/RcfServer::setFileDownloadCustomBandwidthLimit()
to provide a callback function for the RcfServer
to call, whenever a file transfer commences. From the callback funtion you
can assign a relevant bandwidth quota.
For example, imagine we want to implement the following bandwidth limits:
192.168.*.*
.
15.146.*.*
.
We'll need three separate RCF::BandwidthQuota
objects for the three bandwidth quotas described above, and then we'll use
RcfServer::setFileUploadCustomBandwidthLimit()
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.setFileUploadCustomBandwidthLimit(&uploadBandwidthQuotaCb);