-
Notifications
You must be signed in to change notification settings - Fork 27
Example application (RPC)
This example will guide you through the process of designing simple client and server applications. This tutorial assumes that you've already installed the Cap'n Proto tool set and the C# code generator back end (as described in README.md)
Create a new .NET Core console application AddressBookServer
with VisualStudio. Open the NuGet package manager console and install the following packages:
Install-Package Capnp.Net.Runtime
Install-Package CapnpC.CSharp.MsBuild.Generation
Add a new text file to your project, changing its name and extension to AddressBookService.capnp
. Put the following content in it:
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 {
mobile @0;
home @1;
work @2;
}
}
employment :union {
unemployed @4 :Void;
employer @5 :Text;
school @6 :Text;
selfEmployed @7 :Void;
# We assume that a person is only one of these.
}
}
struct AddressBook {
people @0 :List(Person);
}
interface AddressBookService {
createPerson @0 () -> (id: UInt32);
# Creates a new empty person record and returns its ID
updatePerson @1 (person: Person);
# Replaces an existing person record. The person is identified by its ID.
deletePerson @2 (id: UInt32);
# Deletes an existing person record, given its ID
findPersons @3 (name: Text) -> (matches: List(Person));
# Finds all persons whose name contains the supplied string
}
Try to build the project. It will fail with an error message like this:
AddressBookService.capnp(1,1,1,1): error : File does not declare an ID. I've generated one for you. Add this line to your file: @0x81a46f5ce4486da1;
Put the generated ID to the very first line of the schema file. Now, it should begin like this:
@0x81a46f5ce4486da1;
struct Person {
id @0 :UInt32;
name @1 :Text;
...
Build again. Now, it should succeed. All necessary code-behind was generated, and you are ready to start implementing the server. Add a new code file AddressBookServiceImpl.cs
to your project:
using CapnpGen;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Linq;
using System.Collections.Concurrent;
namespace AddressBookServer
{
class AddressBookServiceImpl : IAddressBookService
{
readonly ConcurrentDictionary<uint, Person> _persons = new ConcurrentDictionary<uint, Person>();
uint _nextId;
public Task<uint> CreatePerson(CancellationToken cancellationToken_ = default)
{
_persons.TryAdd(_nextId, new Person());
return Task.FromResult(_nextId++);
}
public Task DeletePerson(uint id, CancellationToken cancellationToken_ = default)
{
if (!_persons.Remove(id, out _))
{
throw new InvalidOperationException("Id does not exist");
}
return Task.CompletedTask;
}
public void Dispose()
{
}
public Task<IReadOnlyList<Person>> FindPersons(string name, CancellationToken cancellationToken_ = default)
{
return Task.Run(() =>
{
var matches = from p in _persons.Values.AsParallel()
where p.Name.Contains(name, StringComparison.CurrentCultureIgnoreCase)
select p;
return (IReadOnlyList<Person>)matches.ToList();
});
}
public Task UpdatePerson(Person person, CancellationToken cancellationToken_ = default)
{
Person existing;
do
{
if (!_persons.TryGetValue(person.Id, out existing))
throw new InvalidOperationException("Id does not exist");
} while (!_persons.TryUpdate(person.Id, person, existing));
return Task.CompletedTask;
}
}
}
AddressBookServiceImpl
implements IAddressBookService
, which was generated from the schema description. Modify Program.cs
such that it starts a Capnp TCP server:
using Capnp.Rpc;
using System;
using System.Net;
namespace AddressBookServer
{
class Program
{
static void Main(string[] args)
{
using (var server = new TcpRpcServer())
{
server.AddBuffering();
server.Main = new AddressBookServiceImpl();
server.StartAccepting(IPAddress.Any, 5555);
Console.WriteLine("Press RETURN to stop listening");
Console.ReadLine();
}
}
}
}
Create a new .NET Core console application AddressBookClient
with VisualStudio. Like with the server application, install the NuGet packages Capnp.Net.Runtime
and CapnpC.CSharp.MsBuild.Generation
. Moreover, add AddressBookService.capnp
from the server application to your project. Build the project for obtaining the code-behind files. Modify Program.cs
such that it starts a Capnp TCP client which interacts with the address book service. It might look like this:
using Capnp.Rpc;
using CapnpGen;
using System;
using System.Threading.Tasks;
namespace AddressBookClient
{
class Program
{
static async Task Main(string[] args)
{
using var client = new TcpRpcClient();
client.AddBuffering();
client.Connect("localhost", 5555);
using var book = client.GetMain<IAddressBookService>();
var person = new Person()
{
Id = await book.CreatePerson(),
Name = "John Doe",
Employment = new Person.employment()
{
which = Person.employment.WHICH.SelfEmployed
}
};
await book.UpdatePerson(person);
try
{
await book.DeletePerson(123);
}
catch (RpcException)
{
Console.WriteLine("As expected, attempting to supply an invalid ID results in an exception.");
}
await book.UpdatePerson(new Person()
{
Id = await book.CreatePerson(),
Name = "Adam Smith",
});
await book.UpdatePerson(new Person()
{
Id = await book.CreatePerson(),
Name = "John Smith",
});
async Task Find(string name)
{
var matches = await book.FindPersons(name);
if (matches.Count == 0)
{
Console.WriteLine($"No persons found matching {name}");
}
else
{
Console.WriteLine($"Found {matches.Count} person(s) matching {name}:");
}
foreach (var match in matches)
{
Console.WriteLine(match.Name);
}
}
await Find("john");
await Find("smith");
await Find("paul");
await book.DeletePerson(0);
await book.DeletePerson(1);
await Find("smith");
}
}
}
Start AddressBookServer
first, then AddressBookClient
. Starting AddressBookServer
for the first time usually triggers a firewall message (because we need to open a TCP port). You'll need to confirm that.