YARP
Yet Another Robot Platform
 
Loading...
Searching...
No Matches
Specialized RPC ports

RPC stands for "Remote Procedure Call", and in YARP is used to refer to communication that consists of a message and a reply to that message.

Historically, YARP has stressed streaming communication (without replies) because in our experience it leads to more robust and scalable control logic. However, RPC definitely has its place, and this tutorial describes how to do it in YARP.

RPC using regular YARP ports

The basic mechanism for getting replies with YARP is described in the getting replies section of the Port Power tutorial. The RPC ports are a thin wrapper around regular ports, for the purpose of restricting flexibility rather than increasing it.

RPC using specialized YARP ports

If you intend to use ports for RPC alone, you can use the yarp::os::RpcClient and yarp::os::RpcServer classes. These are just like the regular yarp::os::Port class, except that streaming operation is prohibited. By using these classes, YARP can check for accidental misusage of the ports, and give more helpful error messages.

Here is an example server:

/*
* SPDX-FileCopyrightText: 2006-2021 Istituto Italiano di Tecnologia (IIT)
* SPDX-FileCopyrightText: 2006-2010 RobotCub Consortium
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* This is an example of using a specialized RpcServer port to receive
* and reply to messages. Regular YARP ports can do this as well (see
* summer.cpp), but use of RpcServer/RpcClient allows for better
* run-time checking of port usage to catch mistakes.
*/
#include <yarp/os/Bottle.h>
#include <yarp/os/Time.h>
#include <cstdio>
using namespace yarp::os;
int main(int argc, char* argv[])
{
if (argc < 2) {
fprintf(stderr, "Please supply a port name for the server\n");
return 1;
}
const char* name = argv[1];
RpcServer port;
port.open(name);
while (true) {
printf("Waiting for a message...\n");
Bottle cmd;
Bottle response;
port.read(cmd, true);
printf("Got message: %s\n", cmd.toString().c_str());
response.addString("you");
response.addString("said");
response.append(cmd);
printf("Sending reply: %s\n", response.toString().c_str());
port.reply(response);
}
}
bool reply(PortWriter &writer) override
Send an object as a reply to an object read from the port.
bool open(const std::string &name) override
Start port operation, with a specific name, with automatically-chosen network parameters.
A simple collection of objects that can be described and transmitted in a portable way.
Definition Bottle.h:64
void append(const Bottle &alt)
Append the content of the given bottle to the current list.
Definition Bottle.cpp:353
void addString(const char *str)
Places a string in the bottle, at the end of the list.
Definition Bottle.cpp:170
std::string toString() const override
Gives a human-readable textual representation of the bottle.
Definition Bottle.cpp:211
A mini-server for performing network communication in the background.
Utilities for manipulating the YARP network, including initialization and shutdown.
Definition Network.h:706
A port that is specialized as an RPC server.
Definition RpcServer.h:23
bool read(PortReader &reader, bool willReply=true) override
Read an object from the port.
Definition RpcServer.cpp:49
An interface to the operating system, including Port based communication.
The main, catch-all namespace for YARP.
Definition dirs.h:16

And here is a client to go with it:

/*
* SPDX-FileCopyrightText: 2006-2021 Istituto Italiano di Tecnologia (IIT)
* SPDX-FileCopyrightText: 2006-2010 RobotCub Consortium
* SPDX-License-Identifier: BSD-3-Clause
*/
/*
* This is an example of using a specialized RpcClient port to send
* messages and receive replies. Regular YARP ports can do this as well,
* but use of RpcServer/RpcClient allows for better
* run-time checking of port usage to catch mistakes.
*/
#include <yarp/os/Bottle.h>
#include <yarp/os/Time.h>
#include <cstdio>
int main(int argc, char* argv[])
{
if (argc < 3) {
fprintf(stderr, "Please supply (1) a port name for the client\n");
fprintf(stderr, " (2) a port name for the server\n");
return 1;
}
const char* client_name = argv[1];
const char* server_name = argv[2];
RpcClient port;
int ct = 0;
while (true) {
if (port.getOutputCount() == 0) {
printf("Trying to connect to %s\n", server_name);
} else {
Bottle cmd;
cmd.addString("COUNT");
cmd.addInt32(ct);
ct++;
printf("Sending message... %s\n", cmd.toString().c_str());
Bottle response;
port.write(cmd, response);
printf("Got response: %s\n", response.toString().c_str());
}
}
}
int getOutputCount() override
Determine how many output connections this port has.
bool write(const PortWriter &writer, const PortWriter *callback=nullptr) const override
Write an object to the port.
void addInt32(std::int32_t x)
Places a 32-bit integer in the bottle, at the end of the list.
Definition Bottle.cpp:140
static bool connect(const std::string &src, const std::string &dest, const std::string &carrier="", bool quiet=true)
Request that an output port connect to an input port.
Definition Network.cpp:682
A port that is specialized as an RPC client.
Definition RpcClient.h:22
void delay(double seconds)
Wait for a certain number of seconds.
Definition Time.cpp:111

The server and client communicate with yarp::os::Bottle objects here, but as usual anything that implements yarp::os::Portable can be used.

Monitoring RPC communication

Normal streaming communication in YARP is very easy to monitor, just connect another port to the sender. For RPC communication, we have to be a bit more careful.

Suppose the server and client above are running (the source code is in example/os/rpc_server/rpc_server.cpp and example/os/rpc_client/rpc_client.cpp), in two separate terminals, as follows:

./rpc_server /server
./rpc_client /client /server

Here we have named the server port /server and the client port /client. Every second or so, the client sends a message to the server with a count in it, and the server replies with "you said" followed by that message.

How can we monitor this traffic remotely? YARP supports a special kind of logging connection called log.in which can report all the inputs a port is receiving (along with the corresponding replies). We make a connection from the server (not the client) to a logging port, set the connection to log.in mode, and we are done.

The syntax for doing so is either:

yarp read /read tcp+log.in://server

Or equivalently:

yarp read /read
yarp connect /server /read tcp+log.in

The protocol in this case is specified to be tcp (which is the default), but with an added +log.in decoration. This is a "carrier modifier" that tells YARP that the connection has a special purpose, in this case to log traffic that arrives at the /server port. Without that modifier, this connection would be forbidden if the RPC server is already connected to a client, and even if it were allowed it would misbehave.

The output shown by the "yarp read" command will be something like this:

[rpc] (COUNT 29) (you said COUNT 29)
[rpc] (COUNT 30) (you said COUNT 30)
[rpc] (COUNT 31) (you said COUNT 31)
[rpc] (COUNT 32) (you said COUNT 32)
[rpc] (COUNT 33) (you said COUNT 33)
[rpc] (COUNT 34) (you said COUNT 34)
[rpc] (COUNT 35) (you said COUNT 35)
[rpc] (COUNT 36) (you said COUNT 36)

The first element is a tag for RPC traffic. The second element is a nested list describing the input received by the port. The third element is a nested list describing the corresponding output produced by the port.

Note that we log from the server side, because we are logging inputs. At this time YARP does not have a log.out mode for logging outputs with their replies.

The logging connection will pass along any envelope or timestamp associated with the input, and will lack an envelope if the input lacks an envelope.