Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
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
● Platform independent format - always in little-endian
● Backwards compatible
● Cross language support
● Strong Typed
● Reference counted
● Time Travel RPC
4. 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
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
#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);
}
7. 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);
}
8. 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;
}
}
}
9. 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
10. 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;
}
}
11. 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);
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 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.