WINDOWS COMMUNICATION FOUNDATION Session és példányosítás
PÉLDÁNYOSÍTÁSI MÓDOK 2
PÉLDÁNYOSÍTÁS A WCF felelős egy bejövő üzenet egy adott szolgáltatás példányhoz kötéséért. Amikor egy kérés érkezik, a WCF eldönti, hogy egy létező szolgáltatás példány fel tudja-e dolgozni a kérést. Ennek eldöntésére egy döntési mátrixot állít fel. 3
PÉLDÁNYKEZELÉS Minden szolgáltatásnak van egy példánykezelési modellje. Három ilyen modell létezik: • az „egyke” (single), amelyben egyetlen CLR objektum szolgálja ki az összes klienst; • a „hívásonkénti” (per call), melyben minden egyes klienshívás kiszolgálására új CLR-objektum jön létre; • és a „munkamenetenkénti” (per session), melynél minden egyes munkamenethez egy kiszolgáló CLR objektum jön létre. A példánykezelési modell kiválasztása függ az alkalmazás által támasztott követelményektől és a szolgáltatás tervezett használatától. 4
PÉLDÁNYKEZELÉS 2. A példányosítás módjának meghatározása a szolgáltatás oldalon történik, azon belül is a szolgáltatás viselkedésének (Service Behavior) leírójában. Ez azt jelenti, hogy a szolgáltatás összes végpontjában ugyanez a példányosítási mód lesz érvényes. 5
HÍVÁSONKÉNTI (PER CALL) MÓD 6
Hívásonként módban minden egyes kérésnek saját szolgáltatás példánya van. A kliens egy proxyn keresztül intéz kéréseket a szolgáltatás felé. Amikor a kérés megérkezik a szolgáltatás hoszthoz, a hoszt a szolgáltatást implementáló osztályból létrehoz egy új példányt. Miután a kérés befejeződött és a válasz is visszaküldésre került a kliens felé, a példányra már nincs szükség. Minden szolgáltatás leíró osztálynak implementálni kell az IDisposable interfészt. Amikor az adott példány elvégezte a dolgát, meghívódik a Dispose metódus. 7
HÍVÁSONKÉNTI (PER CALL) MÓD A WCF-ben a hívásonkénti mód az alapértelmezett. Ennek egyik oka, hogy így a fejlesztőnek nem kell foglalkoznia a konkurens feldolgozással. Viszont számos hátránya is van ennek a módnak: • Teljesítmény szempontjából nem előnyös • Ha egy szolgáltatás példány zárol egy erőforrást a teljes életciklusa idejére, akkor az az erőforrás a többi példány számára elérhetetlenné válik. (pl. fájl, adatbázis, stb.) A problémák kiküszöbölésére léteznek megoldások. 8
HÍVÁSONKÉNTI (PER CALL) MÓD Az egyik megoldás egy proxy objektum rendelése a szolgáltatáshoz. Az alapértelmezett programozási modell szerint, ha egy adott szolgáltatás példányra nincs szükség, akkor arra meghívódik a Dispose metódus. Ez érvényteleníti a referenciát, ami nem mindig kívánatos. A WCF-ben a kliens a proxyra tart fent egy referenciát, ami nem szűnik meg minden hívás után. A proxy feladata az, hogy a szolgáltatás példányt újra létrehozza, ha szükséges. 9
HÍVÁSONKÉNTI (PER CALL) MÓD A példányosítási mód megadási a szolgáltatás oldalán történik: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)] class UpdateService : IUpdateService {...} Habár elméletben a kliensnek nem kell tudnia a példányosítás módjáról, a hívásonkénti példányosítási mód azt jelenti, hogy a hívások között nem őrződik meg az állapot. Ha bármilyen okból mégis szükség van az állapot megőrzésére, a szolgáltatás feladat biztosítani azt. (pl. adatbázisba mentés) 10
MUNKAMENETENKÉNTI (PER SESSION) MÓD 11
MUNKAMENETENKÉNTI (PER SESSION) MÓD Az előbbiekben már felmerült az igény a hívások között az állapotok megőrzésére, így létezik egy munkamenetenkénti (per session) példányosítási mód is. A WCF képes egy privát session-t létesíteni a kliens és a szolgáltatás példány között. Minden klienshez az első kérésekor hozzárendelődik egy szolgáltatás példány és elkezdődik egy munkamenet. Minden további kérés ennek a munkamenetnek a része lesz, és ugyanaz a szolgáltatás példány fogja végrehajtani azt. 12
MUNKAMENETENKÉNTI (PER SESSION) MÓD A munkamenetenkénti mód beállításához 2 dolog szükséges: A szerződés rögzíti, hogy szükséges a munkamenet. Ez azért fontos, mert a kliensnek tartalmazni kell egy azonosítót, hogy megtalálja az adott szolgáltatás objektumot. A munkamenetenkénti mód beállítása a szolgáltatás oldalon: [ServiceContract(SessionMode = SessionMode.Required)] public interface IUpdateService { // Interface definition code goes here } 13
MUNKAMENETENKÉNTI (PER SESSION) MÓD A másik dolog: A WCF-nek is meg kell mondani, hogy munkamenetenkénti módot szeretnénk használni, és a szolgáltatás példányt tartsa meg az egész munkamenet alatt. Ennek megadása a következőképpen történik: [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] public class UpdateService : IUpdateService { // Implementation code goes here } 14
MUNKAMENETENKÉNTI (PER SESSION) MÓD A kapcsolat igazából nem a kliens és a szolgáltatás között áll fenn, hanem egy adott proxy példány között, amit a kliens és a szolgáltatás használ. Amikor a WCF-ben létrehozunk egy proxyt, annak lesz egy azonosítója, amit a szolgáltatás hoszt arra használ, hogy a kéréseket a megfelelő példányokhoz továbbítsa. Mivel ez az azonosító a proxy példányhoz rendelt, így ha a kliens egy proxyból több példányt is létrehoz, azok nem alkotnak egy munkamenetet. Minden proxynak saját szolgáltatás példánya lesz. 15
MUNKAMENETENKÉNTI (PER SESSION) MÓD Ahogy már korábban említettük, a szolgáltatás példány addig létezik, amíg a kliensnek szüksége van rá. Egy munkamenet megszűnésének legtermészetesebb módja, amikor a kliens bezárja a proxyt. Ekkor egy üzenet kerül elküldése a szolgáltatás felé, hogy a munkamenet lezárult. De mi van akkor, ha valamilyen okból a kliens nem zárja be a proxyt? Ebben az esetben a munkamenet 10 percnyi inaktivitás után automatikusan megszűnik. Ezután ha a kliens újra használni szeretné a proxyt, CommunicationObjectFaultedException –t kap. 16
MUNKAMENETENKÉNTI (PER SESSION) MÓD Ez a 10 perc csak egy alapértelmezett érték, a kötéstől függően változhat. Ha a kötés egy megbízható munkamenetet (reliable session) biztosít, akkor az InactivityTimeout tulajdonságot ennek megfelelően átállíthatjuk. Erre egy példa: NetTcpBinding binding = new NetTcpBinding(); binding.ReliableSession.Enabled = true; binding.ReliableSession.InactivityTimeout = TimeSpan.FromMinutes(60); 17
MUNKAMENETENKÉNTI (PER SESSION) MÓD Ugyanezt a config fájlban is megtehetjük: Az InactivityTimeout –ot mind a kliens, mind a szolgáltatás oldalán beállíthatjuk, a kettő közül mindig a kisebb érték a mérvadó. A basicHttpBinding nem támogatja a munkamenetek létrehozását. 18
SINGLETON MÓD 19
SINGLETON MÓD Ebben a módban csak egyetlen szolgáltatás példány jön létre. Ennek az egy példánynak a feladata az összes szolgáltatás felé beérkező kérés feldolgozása. Az a példány örökké él, és csak akkor szűnik meg, amikor a hoszt processz bezáródik. Beállítása: [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class UpdateService : IUpdateService { // Implementation code goes here } 20
SINGLETON MÓD Singleton módban lehetőség nyílik konstruktoron keresztül inicializálni a példányt. Arra is lehetőség van, hogy létrehozzuk a szolgáltatás példányt, még mielőtt a hoszt elindul. Induláskor átadjuk ezt a példányt a hosztnak. A ServiceHost osztály egyik konstruktora egy singleton példányt vár paraméterként. Az így létrehozott hoszt az összes kérést a paraméterében megkapott szolgáltatás példányhoz fogja továbbítani. Ennek megadása: SingletonUpdateService singletonInstance = new SingletonUpdateService(); ServiceHost host = new ServiceHost(singletonInstance); host.Open(); 21
SINGLETON MÓD Ilyen módban más objektumoknak lehetőségük van arra, hogy elérjék a szolgáltatás példány metódusait és beállítsák a paramétereit. A ServiceHost osztály ehhez biztosít egy SingletonInstance property-t, ami az adott példányra hivatkozik. SingletonUpdateService instance = host.SingletonInstance as SingletonUpdateService; instance.Counter += 50; 22
SINGLETON MÓD Ha a lokális hoszt változó nem elérhető, a példány akkor is elérhető. Az OperationContext osztály biztosít egy readonly Host property-t, amiből ugyanúgy elérhető a példány. ServiceHost host = OperationContext.Current.Host as ServiceHost; if (host != null) { SingletonUpdateService instance = host.SingletonInstance as SingletonUpdateService; if (instance != null) instance.Counter += 1; } 23
SINGLETON MÓD Ha minden kérést egyetlen példány szolgál ki, akkor oda kell figyelni a konkurrens hozzáférésre. Ha egy szolgáltatáshoz egyszerre több kérés érkezik, ugyanaz a példány fogja őket feldolgozni, csak különböző szálakon. Ez azt jelenti, hogy az aktuális metóduson kívüli összes változót (az osztály adattagjait) egyszerre több szál is módosíthatja, aminek hatására az értékük nem lesz megfelelő. Ennek elkerülésére különböző technikákat érdemes használni, mint pl. zárolás. 24
MUNKA A PÉLDÁNYOKKAL 25
A SZOLGÁLTATÁS MEGVÉDÉSE Amikor egy szolgáltatás kikerül egy valós közegbe, akarva – akaratlanul is érhetik támadások. „Denial of Service” (DoS) támadás: kimeríti a szolgáltatás erőforrásait, hogy az több bejövő kérést már ne tudjon feldolgozni. A WCF az ilyen problémák enyhítésére számos megoldást nyújt: pl. a bejövő kérések lelassításával, gátolásával vagy erőforrás kvótákkal 26
BEJÖVŐ KÉRÉSEK GÁTOLÁSA Ennek a megoldásnak a célja kétszeres. Először is megvédi a szolgáltatást attól, hogy a kliensek elárasszák kérésekkel. Másrészt pedig egyenletes terhelés elosztást biztosít a szolgáltatásnak. Ezt úgy valósítják meg, hogy korlátozzák a szolgáltatáshoz beérkezhető kérések számát azért, hogy azokat a szolgáltatás belátható időn belül fel tudja dolgozni. A WCF alapbeállítása, hogy nem gátolja a bejövő kéréseket. Ha a gátolás engedélyezett, akkor a WCF minden egyes bejövő kérésnél ellenőrzi a számlálót. 27
BEJÖVŐ KÉRÉSEK GÁTOLÁSA A WCF alapbeállítása, hogy nem gátolja a bejövő kéréseket. Ha a gátolás engedélyezett, akkor a WCF minden egyes bejövő kérésnél ellenőrzi a számlálót. Ha túllépte a beállított értéket, akkor a tovább kéréseket egy várakozó sorra teszi. Amint a számláló értéket a küszöbérték alá csökken, sorra veszi a következőt a várakozási sorról olyan sorrendben, ahogy a kérések beérkeztek. Általában ha a szolgáltatás elérte a maximális értékét, akkor a kliens request time out-ot kap. 28
BEJÖVŐ KÉRÉSEK GÁTOLÁSA A szolgáltatás leírójában 3 beállítás kontrollálja a szolgáltatás által egy időben feldolgozható kérések számát. Ezek mindegyike a config file ServiceThrottlingBehavior részében található. Ezek a következők: • MaxConcurrentCalls: szabályozza az egyszerre feldolgozható kérések számát. Alapértéke 16. Ez az érték minden típusú kérésre érvényes. • MaxConcurrentSessions: szabályozza a sessiont igénylő csatornák maximális számát. Alapértéke 10. Ezen túli session igénylés TimeoutExepction-t dob. Ha a beérkező kérés típusa nem alkalmas session kezelésre (pl. basicHttpBinding), akkor ennek az értéknek nincs jelentősége. • MaxConcurrentInstances: a szolgáltatás példányok maximális számát adja meg. Alapértéke Int32.MaxValue. Ennek hatása a példányosítás módjától függ. Munkamenetenkénti esetben a MaxConcurrentSession érték a mérvadó, Singleton módban pedig az érték mindig 1. 29
BEJÖVŐ KÉRÉSEK GÁTOLÁSA Beállítása a config fájlban: 30
BEJÖVŐ KÉRÉSEK GÁTOLÁSA Beállítás kódból: ServiceHost host = new ServiceHost( typeof(UpdateService),new Uri(" host.AddServiceEndpoint( "IUpdateService",new WSHttpBinding(), String.Empty); ServiceThrottlingBehavior throttlingBehavior = new ServiceThrottlingBehavior(); throttlingBehavior.MaxConcurrentCalls = 10; throttlingBehavior.MaxConcurrentInstances = 10; throttlingBehavior.MaxConcurrentSessions = 5; host.Description.Behaviors.Add(throttlingBehavior); host.Open(); 31
BEJÖVŐ KÉRÉSEK GÁTOLÁSA A szolgáltatás hoszt megnyitása után lehetőség van a bejövő kérések gátolási beállításainak megtekintésére, de azok módosítására nem. Ezt a szolgáltatás diszpécserén keresztül tehetjük meg. A ServiceHost osztály a ChannelDispatchers propertyjében diszpécserek egy kollekcióját biztosítja, amely ChannelDispateched objektumokat tartalmaz. A ChannelDispatcher objektumnak van egy ServiceThrottle tulajdonsága. Ezen a ServiceThrottle objektumon keresztül elérhetőek az összes beállítások. 32
BEJÖVŐ KÉRÉSEK GÁTOLÁSA A beállítások elérése a következőképpen történik: ChannelDispatcher dispatcher = OperationContext.Current.Host.ChannelDispatchers[0] as ChannelDispatcher; ServiceThrottle throttle = dispatcher.ServiceThrottle; Trace.WriteLine(String.Format("MaxConcurrentCalls = {0}", throttle.MaxConcurrentCalls)); Trace.WriteLine(String.Format("MaxConcurrentSessions = {0}", throttle.MaxConcurrentSessions)); Trace.WriteLine(String.Format("MaxConcurrentInstances = {0}", throttle.MaxConcurrentInstances)); 33
KVÓTÁK A kvóta mechanizmus magában foglalja a szolgáltatás által felhasznált memória menedzselését is. A DoS lényege, hogy hatalmas mennyiségű memóriát emészt fel, így a szolgáltatás már nem tudja kiszolgálni a további bejövő kéréseket, melyek így OutOfMemoryException vagy StackOverflowException kivételeket kapnak. Amikor beállítunk egy kvóta értéket, QuotaExceededException keletkezik. Ahelyett, hogy ilyenkor a szolgáltatás leállna, csak szimplán figyelmen kívül hagyja az üzenetet, és megy tovább. 34
KVÓTÁK Számos beállítás van hatással a kvótára: • MaxReceivedMessageSize: ez közvetlenül a kötésre állítódik be. Megadja, hogy mekkora lehet egy üzenet mérete. Alapértéke byte. Értékét akár a config fájlból, akár kódból is beállíthatjuk. o Config fájlból: 35
KVÓTÁK o Kódból: NetTcpBinding binding = new NetTcpBinding(); binding.MaxReceivedMessageSize = ; ServiceHost host = new ServiceHost( typeof(UpdateService), new Uri("net.tcp://localhost:1234/UpdateService")); host.AddServiceEndpoint( "IUpdateService",binding, String.Empty); host.Open(); 36
KVÓTÁK • ReaderQuotas: a kötés ezen tulajdonsága a beérkező üzenetek komplexitásának mértékét határozza meg. o Beállítható tulajdonságok: 37 PropertyAlapértékLeírás MaxDepth32Megadja, hogy az üzenet node-jai milyen mélységig mehetnek le. MaxStringContentLength8192Az üzenetben lévő leghosszabb string max értéke. MaxArrayLength16384Maximum hány érték lehet egy tömbben. MaxBytesPerRead4096Max hány byte értékkel térhet vissza a Read. MaxNameTableCharCount16384Táblanév max hossza.
MŰVELETEK ELHATÁROLÁSA Alapértelmezetten egy session csak azt jelenti, hogy a szolgáltatás meg tudja állapítani, hogy melyik klienstől érkezett a kérés. Azonban vannak esetek, amikor a műveletek végrehajtási sorrendje is számít. A WCF biztosít erre egy mechanizmust a szerződés tervezőjének, mellyel megadhatja, hogy mely metódusok nem lehetnek elsők, vagy utolsók. Erre az IsInitiating és az IsTerminating propertyk szolgálnak, melyeket a OperationContract attribútumon belül állíthatunk. Ha egy metódus IsInitiating attribútum értéke true, és még nem jött létre session, a metódus hívásakor, akkor egyből létrejön egy. Ha már létezik session, akkor a metódus azon belül hívódik meg. 38
MŰVELETEK ELHATÁROLÁSA Ha a metódus IsTerminating attribútum értéke true, amikor a metódus befejezi futását, a session lezárul. Ez még nem jelenti azt, hogy megszűnik a szolgáltatás példány, a kliensnek ettől függetlenül még meg kell hívniuk a proxy Close metódusát. Habár minden további utasítás efelé a proxy felé InvalidOperationException-nel tér vissza. Ezekkel a propertykkel beállítható a műveletek kezdő és végpontja. Az IsInitiating alapértéke true, az IsTerminating alapértéke false. 39
MŰVELETEK ELHATÁROLÁSA Beállítása: [ServiceContract(SessionMode = SessionMode.Required)] public interface IProcessOrders { [OperationContract] void InitializeOrder(int customerId); [OperationContract(IsInitiating=false)] void AddOrderLine(string productId, int quantity); [OperationContract(IsInitiating=false)] double GetOrderTotal(); [OperationContract(IsInitiating=false, IsTerminating=true)] bool SubmitOrder(); } 40
PÉLDÁNY DEAKTIVÁLÁSA 41
PÉLDÁNY DEAKTIVÁLÁSA Ahogy az előző ábrán is látható, egy példány egy Context objektumon belül töltődik be, és a session információk a kliens üzeneteit nem közvetlenül a példányhoz kötik, hanem a Context objektumhoz. Amikor egy session létrejön, akkor a szolgáltatás hoszt egy új kontextust készít. Ez a kontextus megszűnik, ha a session megszűnik. Ez azt jelenti, hogy alapértelmezetten az kontextus addig él, ameddig a benne lévő példány. A WCF lehetőséget nyújt arra, hogy a két életciklust különválasszuk. 42
PÉLDÁNY DEAKTIVÁLÁSA Létrehozható olyan kontextus is, amiben nincs egyetlen példány sem. A kontextus deaktiváció menedzselése az OperationBehavior ReleaseInstanceMode propertyjén keresztül történik. A ReleaseInstanceMode-ban számos érték beállítható: • BeforeCall • AfterCall • BeforeAndAfterCall • None Az alapértelmezett érték a None. Ez azt jelenti, hogy a szolgáltatás példány azután is létezik, miután feldolgozta a kérést. 43
PÉLDÁNY DEAKTIVÁLÁSA BeforeCall mód esetén minden kérés előtt új példány jön létre. Ha egy példány már eleve létezik, akkor deaktiválódik és meghívódik rá a Dispose metódus. Ezalatt a kliens blokkolódik. Az a mód általában akkor használatos, ha a metódus egy kritikus erőforráshoz szeretne hozzáférni és biztosnak kell lenni abban, hogy az összes előző hozzáférés törlődött. AfterCall mód esetén a metódus lefutása után az aktuális példány deaktiválódik és meghívódik rá a Dispose metódus. Ezzel azt biztosíthatjuk, hogy a metódus által használt erőforrást azonnal felszabadítjuk, amint nincs már rá szükség, így a következő hívásnál egyből elérhető lesz. 44
PÉLDÁNY DEAKTIVÁLÁSA BeforeAndAfterCall mód esetén a mód az előbbi kettő ötvözése. Gyakorlatilag ugyanaz, mint a hívásonkénti (per call) példányosítási mód. A különbség csak annyi, hogy ezt a módot metódusonként tudjuk beállítani, így minden egyes metódusra más és más lehet a példányosítási mód. • Beállítása: public class UpdateService : IUpdateService { [OperationBehavior(ReleaseInstanceMode=ReleaseInstanceMode. BeforeAndAfterCall)] public void Update() { // Implementation code goes here } 45
PÉLDÁNY DEAKTIVÁLÁSA Lehetőség van az aktuális szolgáltatás példány futásidejű deaktiválására is. Ehhez az kontextus biztosít egy ReleaseServiceInstance metódust. Amikor ezt meghívódik, az aktuális példány megjelölésre kerül, hogy rá a deaktiválás vár, miután a metódus befejezte a futását. Meghívása: OperationContext.Current.InstanceContext.ReleaseServiceInstance(); 46