Cap’n Proto or: How I
Learned to Stop Worrying
and Love RPC
Razvan Rotari
What it is?
● Serialization protocol
● Like JSON but binary
● Similar to Protocol Buffers (same author)
Features
● Fast
● Platform independent format - always in little-endian
● Backwards compatible
● Cross language support
● Strong Typed
● Reference counted
● Time Travel RPC
Serialization
Uses a schema file to define the message structure.
The schema file is compiled to the target language using capnp.
capnp compile -oc++ schema.capnp
Will generate a schema.capnp.h and schema.capnp.c++ that need to be
included in your application
Serialization
Supported types:
● Void: Void
● Boolean: Bool
● Integers: Int8, Int16, Int32, Int64
● Unsigned Integers: UInt8, UInt16, UInt32, UInt64
● Floating-point: Float32, Float64
● Blobs: Text, Data
● Lists: List(T)
● Structs
● Generic types - Similar to Java Generics
● Unions
● Interfaces
● Methods
No support for dictionaries!
.capnp file example
#id generated by capnp
@0xed1e03e015818faa;
struct Person {
id @0 :UInt32;
name @1 :Text;
email @2 :Text;
phones @3 :List(PhoneNumber);
struct PhoneNumber {
number @0 :Text;
type @1 :Type;
enum Type {
work @0;
mobil @1;
home @2;
}
}
}
struct AddressBook {
contacts @0 :List(Person);
}
How to use it? Write
#include <AddressBook.capnp.h>
void writeAddressBook(int fd) {
::capnp::MallocMessageBuilder message;
AddressBook::Builder addressBook = message.initRoot<AddressBook>(); //Create the
root node
::capnp::List<Person>::Builder people = addressBook.initContacts(1);
Person::Builder ion = people[0];
ion.setName("Ion");
...
// Lists are fixed sized
::capnp::List<Person::PhoneNumber>::Builder ionPhones = ion.initPhones(1);
ionPhones[0].setNumber("0755-555-321");
ionPhones[0].setType(Person::PhoneNumber::Type::MOBILE);
writePackedMessageToFd(fd, message);
}
How to use it? Read
void printAddressBook(int fd) {
::capnp::PackedFdMessageReader message(fd);
AddressBook::Reader addressBook = message.getRoot<AddressBook>();
for (Person::Reader person : addressBook.getContacts()) {
std::cout << person.getName().cStr() << ": " << person.getEmail().cStr()
<< std::endl;
for (Person::PhoneNumber::Reader phone : person.getPhones()) {
std::cout << " " << " phone: " << phone.getNumber().cStr()
<< std::endl;
}
}
}
RPC
● Uses KJ concurrency framework
● Event driven
● Based on event loop, promises and callbacks
● Similar to node.js
● Can be used over TCP or UNIX sockets
RPC .capnp example
@0x952fc0868f401293;
interface User {
//Methods need to include a index number
login @0 (username :Text, password :Text) -> (token :AuthToken);
getAge @1 (token :AuthToken) -> (age :UInt32);
struct AuthToken {
owner @0 :Text;
token @1 :UInt64;
}
}
Server example
class UserImpl final: public User::Server {
public:
kj::Promise<void> login(LoginContext context) override {
if (context.getParams().hasUsername()) // All fields are optional by default
auto userName = context.getParams().getUsername();
auto token = context.getResults().getToken();
token.setToken(40);
return kj::READY_NOW;
}
};
…
capnp::EzRpcServer server(kj::heap<UserImpl>(), “127.0.0.1”, 5923);
auto& waitScope = server.getWaitScope(); //Register an event loop for this thread
kj::NEVER_DONE.wait(waitScope);
Client example
capnp::EzRpcClient client(“127.0.0.1”, 5923);
auto& waitScope = client.getWaitScope();
// Request the bootstrap capability from the server.
User::Client cap = client.getMain<User>();
// Create a request
auto request = cap.loginRequest();
request.setUsername("admin");
request.setPassword("123456");
auto promise = request.send(); // Make a call to the server.
// Wait for the result. This is the only line that blocks.
auto response = promise.wait(waitScope);
Time Travel!
The result of a RPC call can be used
immediately, even before the server receives it.
The only catch is that it can only be used in
another RPC request.
For example:
foo(bar(f())
Will do a single network round trip.
Limitations
● C++11 only
● Poor MSVC support (only serialization)
● Not 1.0 yet, but used in production
QUESTIONS?

Cap'n Proto (C++ Developer Meetup Iasi)

  • 1.
    Cap’n Proto or:How I Learned to Stop Worrying and Love RPC Razvan Rotari
  • 2.
    What it is? ●Serialization protocol ● Like JSON but binary ● Similar to Protocol Buffers (same author)
  • 3.
    Features ● Fast ● Platformindependent format - always in little-endian ● Backwards compatible ● Cross language support ● Strong Typed ● Reference counted ● Time Travel RPC
  • 4.
    Serialization Uses a schemafile to define the message structure. The schema file is compiled to the target language using capnp. capnp compile -oc++ schema.capnp Will generate a schema.capnp.h and schema.capnp.c++ that need to be included in your application
  • 5.
    Serialization Supported types: ● Void:Void ● Boolean: Bool ● Integers: Int8, Int16, Int32, Int64 ● Unsigned Integers: UInt8, UInt16, UInt32, UInt64 ● Floating-point: Float32, Float64 ● Blobs: Text, Data ● Lists: List(T) ● Structs ● Generic types - Similar to Java Generics ● Unions ● Interfaces ● Methods No support for dictionaries!
  • 6.
    .capnp file example #idgenerated by capnp @0xed1e03e015818faa; struct Person { id @0 :UInt32; name @1 :Text; email @2 :Text; phones @3 :List(PhoneNumber); struct PhoneNumber { number @0 :Text; type @1 :Type; enum Type { work @0; mobil @1; home @2; } } } struct AddressBook { contacts @0 :List(Person); }
  • 7.
    How to useit? Write #include <AddressBook.capnp.h> void writeAddressBook(int fd) { ::capnp::MallocMessageBuilder message; AddressBook::Builder addressBook = message.initRoot<AddressBook>(); //Create the root node ::capnp::List<Person>::Builder people = addressBook.initContacts(1); Person::Builder ion = people[0]; ion.setName("Ion"); ... // Lists are fixed sized ::capnp::List<Person::PhoneNumber>::Builder ionPhones = ion.initPhones(1); ionPhones[0].setNumber("0755-555-321"); ionPhones[0].setType(Person::PhoneNumber::Type::MOBILE); writePackedMessageToFd(fd, message); }
  • 8.
    How to useit? Read void printAddressBook(int fd) { ::capnp::PackedFdMessageReader message(fd); AddressBook::Reader addressBook = message.getRoot<AddressBook>(); for (Person::Reader person : addressBook.getContacts()) { std::cout << person.getName().cStr() << ": " << person.getEmail().cStr() << std::endl; for (Person::PhoneNumber::Reader phone : person.getPhones()) { std::cout << " " << " phone: " << phone.getNumber().cStr() << std::endl; } } }
  • 9.
    RPC ● Uses KJconcurrency framework ● Event driven ● Based on event loop, promises and callbacks ● Similar to node.js ● Can be used over TCP or UNIX sockets
  • 10.
    RPC .capnp example @0x952fc0868f401293; interfaceUser { //Methods need to include a index number login @0 (username :Text, password :Text) -> (token :AuthToken); getAge @1 (token :AuthToken) -> (age :UInt32); struct AuthToken { owner @0 :Text; token @1 :UInt64; } }
  • 11.
    Server example class UserImplfinal: public User::Server { public: kj::Promise<void> login(LoginContext context) override { if (context.getParams().hasUsername()) // All fields are optional by default auto userName = context.getParams().getUsername(); auto token = context.getResults().getToken(); token.setToken(40); return kj::READY_NOW; } }; … capnp::EzRpcServer server(kj::heap<UserImpl>(), “127.0.0.1”, 5923); auto& waitScope = server.getWaitScope(); //Register an event loop for this thread kj::NEVER_DONE.wait(waitScope);
  • 12.
    Client example capnp::EzRpcClient client(“127.0.0.1”,5923); auto& waitScope = client.getWaitScope(); // Request the bootstrap capability from the server. User::Client cap = client.getMain<User>(); // Create a request auto request = cap.loginRequest(); request.setUsername("admin"); request.setPassword("123456"); auto promise = request.send(); // Make a call to the server. // Wait for the result. This is the only line that blocks. auto response = promise.wait(waitScope);
  • 13.
    Time Travel! The resultof a RPC call can be used immediately, even before the server receives it. The only catch is that it can only be used in another RPC request. For example: foo(bar(f()) Will do a single network round trip.
  • 14.
    Limitations ● C++11 only ●Poor MSVC support (only serialization) ● Not 1.0 yet, but used in production
  • 15.