-
Notifications
You must be signed in to change notification settings - Fork 1
Communication through Network via NetGate
Message delivery among different ActorSystems is carried out by NetGate. An ActorSystem can associate with multiple NetGates which communicate with different remote ActorSystems. The basic philosophy of communication via NetGate is that, if actor A wants to send a message to a remote actor B, A first serializes and sends the message to a Sender actor in the NetGate, the Sender forwards the message bytes to the Receiver actor in the NetGate at the side of B, the Receiver forwards the message to B, then B deserializes the messages and processes it.
For the default implementation of NetGate in ZAF, the Sender/Receiver is essentially PUSH/PULL sockets. If a NetGate connects with multiple peer NetGates, it creates multiple pairs of PUSH/PULL sockets, one for each peer, so that the message delivery can be parallelized.
That sending a message to a remote actor is the same as we send a message to a local actor, where what we need is a Actor handle for the remote actor. However, the remote actor is spawned in a remote ActorSystem, how do we get the Actor handle of it? The solution is actor registraion and actor lookup.
A local actor can register itself to a local NetGate, by sending a regisration message with code NetGate::ActorRegistration and the name of the actor. In this way, the local actor is exposed to the outside world. To obtain an actor registered in a remote NetGate, we send a actor lookup message to the local NetGate with code NetGate::ActorLookupReq, the url of the remote NetGate and the name of the actor we want. Once found, the local NetGate will reply a message with code NetGate::ActorLookupRep and the actor. Instead of sending and receiving the messages with specified codes, it's suggested to use NetGateClient to interact with NetGate.
// In ActorSystem A
NetGate gate{actor_system, "x.y.z.w", port};
Actor actor = actor_system.spawn<NewActor>();
gate.register_actor("new_actor", actor); // register the new_actor in NetGate("x.y.z.w:port") with name "new_actor"
// In ActorSystem B
NetGate gate{actor_system, "a.b.c.d", port};
Actor actor = actor_system.spawn(
[&, client = NetGateClient{gate.actor()}](ActorBehavior& self) {
// send a NetGate::ActorLookupReq to local NetGate
client.lookup_actor(self, "x.y.z.w:port", "new_actor");
// wait for the NetGateActorLookupRep from local NetGate
self.receive_once({
// process the reply
client.on_lookup_actor_reply([&](std::string remote_net_gate_url, std::string actor_name, Actor remote_actor) {
// `remote_actor` can be used to send messages to the `new_actor` in ActorSystem A.
...
});
});
});You may also check the example here.
Actor can be safely delivered among actors in the same ActorSystem, no matter whether Actor refers to a local one or a remote once. However, Actor cannot be delivered among different ActorSystems, due to many reasons, e.g., we do not know which NetGate will be used for communication with the delivered Actor and we do not know if the NetGate to be used is connected with the NetGate on the side of the delivered Actor.
Therefore, to deliver an Actor from one ActorSystem to another, we have two steps:
-
When sending an
Actor, convert theActortoActorInfoand send theActorInfo. This is done byActor::to_actor_info, which requires the actor that asks for the ActorInfo, because it makes difference if it's a remote actor or a local actor that asks for a remote actor or a local actor. -
When receiving an
ActorInfo, convert theActorInfotoActorbefore sending messages to theActor. This is done by sending a mesasge to the NetGate with codeNetGate::RetrieveActorReqand theActorInfo, where we will receive a message with codeNetGate::RetrieveActorRepand theActor.
Instead of sending and receiving the messages with specified codes, it's suggested to use NetGateClient to interact with NetGate. Here is an example that delivers Actor a in ActorSystem A from b in the same ActorSystem to c in ActorSystem B.
// In ActorSystem A
Actor a = actor_system.spawn<ActorA>();
Actor b = actor_system.spawn([&](ActorBehavior& self) {
self.receive_once({
AskForActorInfo - [&]() {
this->reply(ReplyWithActorInfo, a.to_actor_info(this->get_current_sender_actor()));
}
});
});
gate.register("b", b);
// In ActorSystem B
NetGate gate{actor_system, "a.b.c.d", port};
Actor c = actor_system.spawn(
[&, client = NetGateClient{gate.actor()}](ActorBehavior& self) {
Actor b = ... ; // supposed we have fetched the remote actor named "b" in ActorSystem A.
self.request(b, AskForActor).on_reply({
ReplyWithActorInfo - [&](ActorInfo info) {
// Send a request `NetGate::RetrieveActorReq` to local NetGate
client.retrive_actor(Requester{self}, info).on_reply({
// Wait for a response `NetGate::RetrieveActorRep` from local NetGate
client.on_retrieve_actor_reply([&](ActorInfo, Actor remote_a) {
// remote_a can be used to send messages to Actor a in ActorSystem A
...
});
});
}
});
});