C# - Kliens által aktivált távoli objektum

forráskód letöltése
A .NET Remoting szolgáltatásának segítségével építhetünk fel kapcsolatot két program között valamilyen szoftvercsatornán keresztül. A kliens alkalmazás kéréssel fordul a szerver felé, mely képes kiszolgálni azt. Ez a kiszolgálás valamilyen objektum segítségével történik. Cikkünkben azt fejtegetjük, hogy miként aktiválhatja ezt az objektumot a kliens, megadva az objektum élettartamára vonatkozó adatokat.
A mellékelt példa teljes fordítása után a CAOClient projekt futtatása előtt indítsa el a CAOServer projekt alkalmazását.
Elméleti áttekintés
A mellékelt példa ismertetése előtt át kell tekintsünk néhány elméleti alapfogalmat. Először lássuk mit is jelent az, hogy kliens által aktivált objektum.
A szerver aktiválta objektumokkal ellentétben, ahol a kliensnek semmiféle kontrolálási lehetősége nincs az objektummal kapcsolatban, a kliens által aktivált objektumok esetén van lehetőség arra, hogy a kliens befolyásolja az objektum életciklusát.
Még egy nagyon fontos dolgot kell itt megemlítenünk az objektum létrejöttével kapcsolatban. A szerver kontrollálta objektumok akkor jönnek létre egy nagyon lassú folyamat eredményeként, mikor a kliens használni kívánja azokat, valamely metódusának meghívásával. A kliens aktiválta objektumok egy gyorsabb folyamat eredményeként jönnek létre, mintegy szinkron módon.
A kliens kontrollálta objektum adott klienshez van rendelve. Ez azt jelenti, hogy a hívó kliensek nem osztoznak egy objektum példányon, hanem minden klienshez létrejön egy objektum példány. 1000 darab kliens esetén ez 1000 objektum példányt jelent.
Szálak esetén ez ugyancsak így van. Amennyiben a kliens több szállal rendelkezik, és minden szál hívja az objektumot, akkor minden szál megkapja a saját objektum példányát.
A kliens aktiválta objektumok élettartamára a klienseknek egy bizonyos fokig van hatásuk. Itt a metódushívások között tárolódnak bizonyos kliens-specifikus állapotinformációk az objektum élettartama alatt.
A Marshal-by-reference objektumok (MBR) nem tartózkodnak örökké a memóriában. Ehelyett a kliens aktiválta objektumokban felülírjuk a MarshalByRefObject osztály InitializeLifetimeService metódust, saját életciklus szabályokat deklarálva az objektumhoz. Ahogy a példában is látni fogjuk, úgynevezett LEASE és SPONSOR objektumok egymást kiegészítő felügyelete jellemző az objektum életciklusának menedzselésére.
A LEASE objektum jelenti azt az időintervallumot, ameddig egy objektum a memóriában marad aktív állapotban, mielőtt meghívódna a .NET Remoting objektumtörlő mechanizmusa. A szerver alkalmazásban van egy úgynevezett LEASE MANAGER, mely megállapítja, hogy egy adott objektum ki van-e jelölve szemétgyűjtésre, vagy sem. A SPONSOR objektum eszközöl egy új kérést, majd regisztrálja a létrejövő LEASE-t.
Ezek a LEASE-ek minden külső kéréskor létrejönnek az objektumhoz. A LEASE MANAGER periodikusan megvizsgálja, hogy az egyes lease-intervallumok mikor járnak le. Amikor egy időintervallum lejár, akkor a menedzser megkérdezi a szponzorokat, hogy megújítják-e azt. Ha nem, akkor a szemétgyűjtő mechanizmus elpusztítja az objektumot.
Az objektumok élettartama független a kliensekétől, így a rövid lease-intervallummal felruházott objektumok folyamatosan használatban vannak egy-egy kliens által, mely hatékony, a hálózatot kevésbé terhelő kezelést tesz lehetővé.
Ezzel a mechanizmussal a távoli objektumok élettartama megbízhatóan szabályozható, még kevésbé megbízható hálózati kapcsolatok esetén is kellő biztonságot nyújt.
Példa alkalmazás
A példában a kliens által felügyelt életciklussal rendelkező objektumot hívunk meg a kliens alkalmazásból. A szerver alkalmazás egy konzol program, mely csak figyel a meghatározott porton. A kliens egy dialógusablakkal rendelkező Windows-os program, melynek Start gombjára kattintva hívható a távoli objektum metódusa.
A projektek mindegyikében meg kell adni referenciaként a .NET Remoting szolgáltatásának igénybevételéhez szükséges osztályokat tartalmazó névteret. Ehhez válasszuk a Project - Add reference menüpontot, majd a megjelenő ablakban a .NET lapon keressük elő a System.Runtime.Remoting elemet. A forráskódot tartalmazó állományok elején meg kell adnunk, hogy a névtér osztályai közül melyekre van szükségünk.
A példában három projektet készítünk el, melyek tartalmazzák a kommunikációban résztvevő alkalmazások kódjait.
CAOType projekt
A projekt tartalmazza a távoli objektum osztályát, valamint a meghívandó metódusainak deklarációját. Az objektum élettartamára vonatkozó információkat kell itt megadnunk, melyhez fel kell használnunk a System.Runtime.Remoting.Lifetime névtér elemeit, így ezt meg kell adnunk hivatkozásként. Az objektum konstruktorában és destruktorában is szöveget írunk a konzolra, hogy jelezzük, mikor jött létre, illetve pusztult el az objektum.
Deklaráljuk az InitializeLifetimeService metódust:
public override Object InitializeLifetimeService()
{
Megadjuk az élettartam adatokat tartalmazó objektumot:
  ILease lease = (ILease)base.InitializeLifetimeService();
  if (lease.CurrentState == LeaseState.Initial)
  {
Megadjuk a kezdeti értéket:
    lease.InitialLeaseTime = TimeSpan.FromSeconds(3);
Megadjuk azt az időt, amíg a lease menedzser vár egy szponzorra:
    lease.SponsorshipTimeout = TimeSpan.FromSeconds(10);
Azt az értéket, melynek el kell telnie két hívás közt:
    lease.RenewOnCallTime = TimeSpan.FromSeconds(2);
  }
  return lease;
}
Az objektum GetUser metódusát hívja a kliens, mely visszaadja, hogy éppen mely felhasználó futtatja a process-t.
CAOServer project
A Remoting használatához szükségünk lesz az alábbi névterek használatára:
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Lifetime;
using System.Runtime.Remoting.Channels;
Megadásuk után a System.Runtime.Remoting.Channels.Tcp-t nem találja a fordító, így erre külön hivatkoznunk kell egy referencia hozzáadásával. Válasszuk tehát Project - Add reference menüpontot, majd a megjelenő ablakban a .NET lapon keressük elő a System.Runtime.Remoting elemet. Select és OK gomb után már nem reklamál a fordító.
Szükségünk lesz az CAOType-ban létrehozott osztály eléréséhez is. Erre szintén hivatkoznunk kell:
using CAOType;
Mivel a CAOType névtér sem található, így egy újabb referencia hozzáadásával megoldhatjuk ezt a problémát is. Válasszuk ismét a Project - Add reference menüpontot, majd a megjelenő ablakban a Projects lapon jelöljük ki az CAOType elemet.
A CAOServer alkalmazás indulásakor meg kell adni bizonyos időtartam adatokat:
LifetimeServices.LeaseTime = TimeSpan.FromMinutes(1);
LifetimeServices.RenewOnCallTime = TimeSpan.FromMinutes(1);
Regisztrálnunk kell egy HTTP portot, melyen keresztül az alkalmazásunk elérhető lesz. Ehhez létrehozunk egy új HttpChannel osztályt, melynek konstruktorában adjuk meg a használni kívánt port számát.
ChannelServices.RegisterChannel(new HttpChannel(9999));
A RemotingConfiguration osztály RegisterActivatedServiceType függvényét felhasználva elvégezhetjük a regisztrációt. Itt meg kell adnunk az objektum típusát:
RemotingConfiguration.RegisterActivatedServiceType(typeof(CAOType.ClientActivatedType));
Ezzel a szerver alkalmazásunk el is készült, futtathatjuk.
CAOClient projekt
A kliens alkalmazás futtatása előtt mindig el kell indítani a szervert is. Meg kell adnunk a hivatkozott névterek listáját, hogy a szükséges osztályokat elérjük:
using System.Runtime.Remoting.Lifetime;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Activation;
using System.Runtime.Remoting.Channels.Http;
Szükségünk lesz az CAOType-ban létrehozott osztály eléréséhez is. Erre szintén hivatkoznunk kell:
using CAOType;
Mivel a CAOType névtér sem található, így egy újabb referencia hozzáadásával megoldhatjuk ezt a problémát is. Válasszuk ismét a Project - Add reference menüpontot, majd a megjelenő ablakban a Projects lapon jelöljük ki az CAOTypet elemet.
A Start gombra kattintva a következő folyamatokat hajtjuk végre. Felépítjük a kapcsolatot a http port végén figyelő szerverrel:
HttpChannel httpCh = new HttpChannel();
ChannelServices.RegisterChannel(httpCh);
object[] attrs = {new UrlAttribute("http://localhost:9999/CAOType")};
Felveszünk egy kezelőt a távoli objektum számára, majd példányosítjuk azt:
ObjectHandle handle = null;
...
handle = Activator.CreateInstance("CAOType","CAOType.ClientActivatedType",attrs);
ClientActivatedType obj = (ClientActivatedType)handle.Unwrap();
A Sponsor.cs állományban deklaráltunk egy szponzor osztályt a folyamathoz, melyből egy példányt most létrehozunk:
  MyClientSponsor sponsor = new MyClientSponsor(TimeSpan.FromSeconds(10));
Az objektumnak pedig lekérdezzük az élettartam-információit, és megjelenítjük azt a RichTextBox kontrolban, az objektum GetUser metódusának eredményével együtt:
ILease lease = (ILease)obj.GetLifetimeService();
A lease objektum InitialLeaseTime, RenewOnCallTime és SponsorshipTimeout property-jeit lekérdezzük, és az AppendText metódussal írjuk a RichTextBox-ba.
Végezetül regisztrálnunk kell a szponzort:
lease.Register(sponsor);
A szponzor osztály deklarációjakor az osztályban az ISponsor interfész tulajdonságait kell implementálni.