C# - Remoting, avagy távoli függvényhívások megvalósítása

forráskód letöltése
Ha adott egy alkalmazás mely fut mondjuk az „A” gépen és a helyi TCP hálózatban ezt a gépet elérheti a „B” gép, akkor lehetőségünk van olyan alkalmazás készítésére, mely a „B” gépen futva az „A” gépen futó alkalmazás egy függvényét meghívja úgy, mintha nem is két programról, hanem csak egyről lenne szó.
A Remoting használatával ezt a feladott igen egyszerűen megvalósíthatjuk, ráadásul helyi hálózat helyett akár az internetet is használhatjuk és HTTP-n keresztül is hívogathatunk függvényeket.
Nézzünk kezdetnek egy alapszintű példát és annak megvalósítását. Legyen a feladat a következő: legyen egy alkalmazás, melynek egy függvénye képes a paraméterként kapott két számot összeadni, majd ennek eredményét visszatérési értékként szolgáltatni. Legyen egy másik alkalmazás, mely meghívja az elsőnek ezt a függvényét és elvégeztet vele összeadásokat, majd megjeleníti a kapott eredményt.
Ha csak egy alkalmazásról lenne szó, akkor ez egy igen egyszerű feladat lenne. Most viszont két külön alkalmazást kell készítenünk, mely ráadásul lehet, hogy nem is ugyanazon a gépen fog futni. Természetesen működhet egy gépen is a két program.
Mivel a kommunikáció a két program között TCP-n keresztül valósul meg, így választanunk kell egy olyan port számot, mely az adott gépeken még nem használt. Mint látni fogjuk, e kommunikáció lebonyolításával nem kell foglalkoznunk. Nekünk csak az a feladatunk, hogy némi előkészítő munkálat után egyszerűen meghívjuk az elérni kívánt függvényt.
Nézzük mit is kell tennünk.
A mellékelt példában három projekt kapott helyet.
RObject projekt
Készítsünk első lépésként egy DLL-t, melyben egy egyszerű osztályt hozunk létre, amely tartalmazni fogja azt a függvényt, melyet majd a későbbiek folyamán más alkalmazásokból is elérhetünk. A létrehozott osztályt RemoteClass-nak nevezzük el. Fontos követelmény, hogy ezt a távoli elérés érdekében a MarshalByRefObject osztályból származtassuk.
  public class RemoteClass: MarshalByRefObject 
  {
Az osztályon belül létrehozunk egy publikus AddMethod nevű függvényt, mely két egész számot vár paraméterként. Ezt a két számot összeadva egy sztringet fog szolgáltatni eredményként.
    public String AddMethod(int a, int b) 
    {
      return a.ToString() + " + " + b.ToString() + " = " + (a+b).ToString();
    }
  }
Ezzel a projekttel más tennivalónk nincs is. Fordítsuk le a DLL-t.
RServer projekt
Ez lesz az az alkalmazás, mely mint egy szerver szolgáltatja a függvényt, figyeli a beérkező kéréseket az általunk megadott TCP porton.
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;
using System.Runtime.Remoting.Channels.Tcp;
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 RObject-ben létrehozott osztály eléréséhez is. Erre szintén hivatkoznunk kell:
using RObject;
Mivel az RObject 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 RObject elemet.
Az előkészületek után már jöhet a lényegi munka. Az RServer alkalmazás indulásakor regisztrálnunk kell egy TCP portot, melyen keresztül az alkalmazásunk elérhető lesz. Ehhez létrehozunk egy új TcpChannel osztályt, melynek konstruktorában adjuk meg a használni kívánt port számát.
      TcpChannel tc = new TcpChannel(9001);
A ChannelServices osztály RegisterChannel függvényének segítségével regisztrálhatjuk ezt a portot az alkalmazásunk számára.
      ChannelServices.RegisterChannel(tc);
Befejező lépésként már csak az marad hátra, hogy regisztráljuk azt az osztályt, melynek függvényét elérhetővé kívánjuk tenni. Ehhez szükségünk lesz az RObject névtér RemoteClass osztályának típusára, Type formában.
      Type t = typeof(RObject.RemoteClass);
A RemotingConfiguration osztály RegisterWellKnownServiceType függvényét felhasználva elvégezhetjük a regisztrációt. Itt első paraméterként megadjuk az osztály típusát, másodikként egy azonosító nevet, melyet a híváskor kell majd felhasználnunk, végül a hívás módjáról rendelkezhetünk.
      RemotingConfiguration.RegisterWellKnownServiceType(t, "Add", WellKnownObjectMode.SingleCall);
Ezzel a szerver alkalmazásunk el is készült, futtathatjuk.
RClient projekt
Nem marad más hátra, mint egy kliens alkalmazás megírása, hogy a már elkészült szervert ki is próbálhassuk. Itt szintén szükségünk lesz a Remoting névterek, illetve az RObject névtér elérésére a fentiekben leírt módon.
Ne felejtsük el, hogy e kliens alkalmazás csak akkor érheti el a kívánt függvényt, ha az RServer alkalmazás már fut. Ezért a kapcsolódás előtt indítsuk el a szerver programunkat mindig.
A kliens feladatát két részre osztottuk. Elsőként létrehozunk egy kapcsolatot a szerverrel, vagyis megpróbáljuk elérni az adott porton figyelő alkalmazást.
Ehhez ismét egy TcpChannel osztályra lesz szükségünk, valamint az Activator osztály GetObject függvényére. Ez utóbbi képes arra, hogy egy adott távoli objektumot aktiváljon és arról egy referenciát visszaadjon. Első paraméterként az elérendő objektum típusát kell megadnunk itt is Type típusban. Második paraméterben egy kapcsolódási URL-t adunk meg, melyben a TCP protokollon keresztül elérjük az adott gépen futó, általunk választott porton figyelő alkalmazást. A kapcsolódási sztring végén látható az Add szó. Ez az az azonosító, melyet a szerver alkalmazásban a RegisterWellKnownServiceType függvény második paramétereként megadtunk.
    private void button2_Click(object sender, System.EventArgs e)
    {
      TcpChannel tc = new TcpChannel();
      ChannelServices.RegisterChannel(tc);
      rc = (RemoteClass)Activator.GetObject(typeof(RObject.RemoteClass), "tcp://localhost:9001/Add");
      ...
    }
A kapcsolat felvétel után már nincs más teendőnk, mint a kapott objektum AddMethod függvényét a megfelelő paraméterezéssel meghívnunk. A forráskódon jól megfigyelhető, hogy e távoli függvény hívása pont úgy történik, mintha az egy a saját alkalmazásban lévő objektum egy függvénye lenne. Fenti előkészítő munkálatokat figyelmen kívül hagyva, valóban nincs is más teendőnk egy távoli függvény hívásakor, mint egy helyi függvény esetében.
    private void button1_Click(object sender, System.EventArgs e)
    {
      label2.Text=rc.AddMethod(Convert.ToInt32(textBox1.Text), Convert.ToInt32(textBox2.Text));
    }