Akka .NET Remote duplex

Jag har tidigare visat hur man skickar enkla meddelanden med Akka .NET Remote. Exemplet bestod av tre projekt, en server, ett datalager och en klient. Här tänkte jag visa hur man använder Akka .NET Remote för att både skicka och ta emot meddelanden. I detta exempel återanvänder jag servern BioServer och datalagret BioData från det förra exemplet.

vs

Tanken är alltså att programmet är ett enkelt bokningssystem för en liten biograf med tre sittplatser. Denna gång tänker jag utöka datalagret med lite nya meddelanden och servern med kapaciteten att svara på inkommande anrop. Dessutom bygger jag en ny grafisk klient.

Datalagret
I den förra versionen hade vi bara ett enda definierat meddelande i datalagret, klassen Bokningsmeddelande. Detta meddelande finns kvar, men nu kompletterar vi med Bokningssvar som ska skickas tillbaka till klienten som skickar in ett Bokningsmeddelande.

public class Bokningsmeddelande
{
   public int StolNr { get; private set; }
   public int BokadAv { get; private set; }
   public Bokningsmeddelande(int stolNr, int bokadAv)
   {
      this.StolNr = stolNr;
      this.BokadAv = bokadAv;
   }

   public override string ToString()
   {
      return string.Format(
         "Bokning från {0} på stol {1}.",
         this.BokadAv, this.StolNr);
   }
}

public class Bokninssvar
{
   public bool BokningAccepterad { get; private set; }
   public int StolNr { get; private set; }
   public Bokninssvar(bool bokningAccepterad, int stolNr)
   {
      this.BokningAccepterad = bokningAccepterad;
      this.StolNr = stolNr;
   }
}

Servern
Låt oss börja med att implementera stödet för Bokningsmeddelande och Bokningssvar.

Klassen Stollista, alltså den bokningsbara resursen, har jag valt att utöka med en funktion som kan uppge vem som bokat en plats.

public int BokadAv(int stolNr)
{
   var stol = this.Find(m => m.StolNr == stolNr);
   if (stol == null)
      return 0;
   else
      return stol.BokadAv;
}

Det innebär att hela klassen nu ser ut så här:

class Stollista : List
{
   public override string ToString()
   {
      var ret = new System.Text.StringBuilder();
      foreach (var s in this)
         ret.AppendLine(s.ToString());
      return ret.ToString();
   }

   public bool Boka(int stolNr, int bokadAv)
   {
      var stol = this.Find(m => m.StolNr == stolNr);
      if (stol == null)
         return false;
      stol.BokadAv = bokadAv;
      return true;
   }

   public int BokadAv(int stolNr)
   {
      var stol = this.Find(m => m.StolNr == stolNr);
      if (stol == null)
         return 0;
      else
         return stol.BokadAv;
   }
}

Dessutom har jag modifierat aktören Bokningsaktor så att den kontrollerar huruvida den önskade stolen är bokningsbar, och meddelar klienten om bokningen accepteras. Notera hur aktören använder propertyn Sender för att kommunicera med avsändaren.

class Bokningsaktor : Akka.Actor.ReceiveActor
{

   public Bokningsaktor()
   {
      this.Receive(b => {
         Console.WriteLine("Ny bokning!");
         if (Program.stolar.BokadAv(b.StolNr) == 0 ||
            Program.stolar.BokadAv(b.StolNr) == b.BokadAv)
         {
            Program.stolar.Boka(b.StolNr, b.BokadAv);
            this.Sender.Tell(new BioData.Bokninssvar(true, b.StolNr));
         }
         else
            this.Sender.Tell(new BioData.Bokninssvar(false, b.StolNr));
         Console.WriteLine(Program.stolar.ToString());
      });
   }
}

Den nya klienten
I detta fall vill jag ha en grafisk klient för att kunna representera svaren från servern på ett någorlunda snyggt sätt. Jag har skapat en applikation av typen Windows Forms Application, satt referenser till Akka .NET och Akka .NET Remote med hjälp av NuGet samt satt en referens till datalagret BioData. Dessutom vill jag läsa in namnrymden Akka.Actor för att komma åt en extension method – en överlagring av funktionen ActorOf.

using Akka.Actor;

Vidare väljer jag att hålla en referens till mitt aktörssystem i huvudformuläret.

private Akka.Actor.ActorSystem system;

På formuläret har jag placerat ut ett textfält där användaren förväntas infoga sitt användar-ID (ett heltal) samt tre knappar som representerar de tre bokningsbara stolarna. Man förväntas ange sitt ID i textfältet innan man klickar på någon knapp.

form

I formulärets Load-event initierar jag aktörssystemet på samma sätt som tidigare, men sparar nu referensen i medlemsvariabeln som jag deklarerade ovan.

private void Form1_Load(object sender, EventArgs e)
{
   var config = Akka.Configuration.ConfigurationFactory.ParseString(@"
akka {
    actor {
        provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
    }
    remote {
        helios.tcp {
            port = 0
            hostname = localhost
        }
    }
}
");
   this.system = ActorSystem.Create("Bokningsklient", config);         
}

Eventet FormClosed får sköta uppstädningen.

private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
   this.system.Shutdown();
   this.system.Dispose();
}

Sedan ska Click-eventet under varje knapp inleda bokningsförfarandet.

private void btnStol1_Click(object sender, EventArgs e)
{
   this.Boka(1);
}

private void btnStol2_Click(object sender, EventArgs e)
{
   this.Boka(2);
}

private void btnStol3_Click(object sender, EventArgs e)
{
   this.Boka(3);
}

För att aktören ska kunna komma åt knapparna direkt, har jag även en enkel funktion som tar ett stolsnummer och ger en referens till en knapp.

public Button Stolreferens(int StolNr)
{
   Button[] ret = { this.btnStol1, this.btnStol2, this.btnStol3 };
   return ret[StolNr - 1];
}

Sista funktionen i formuläret är den som utför själva bokningen. Notera att vi skickar med en referens till en lokal aktör som ska kunna ta emot svaret från servern.

private void Boka(int StolNr)
{
   var avsandare = this.system.ActorOf();
   var bokare = this.system.ActorSelection(
      "akka.tcp://AllaBokare@localhost:8080/user/Bokare");
   bokare.Tell(new BioData.Bokningsmeddelande(StolNr,
      int.Parse(txtAnvID.Text)), avsandare);
}

Den lokala aktören ser ut så här. Notera att den innehåller lite fulkod för att erhålla en referens till formuläret i fråga. Koden förmodar att formuläret är ensamt i programmet.

public class LocalActor : Akka.Actor.ReceiveActor
{
   public LocalActor()
   {
      this.Receive(s =>
      {
         var stol = (Application.OpenForms[0]
            as Form1).Stolreferens(s.StolNr);
         stol.BackColor = s.BokningAccepterad ? Color.Green : Color.Red;
      });
   }
}

Varje klient som klickar på en knapp för att utföra en bokning, kommer att få en bekräftelse genom att knappen blir grön. Om stolen inte är bokningsbar blir istället knappen röd.

form2

Leave a Reply

Your email address will not be published. Required fields are marked *