23. fejezet - Input/Output műveletek

Tartalom

A központ és a külvilág.
A Stream fogalma
Az eldobható osztály
A FileStream osztály
A MemoryStream osztály I.
A MemoryStream osztály II.
A CryptoStream osztály
A GZipStream osztály
A Deflate compression
System Environment demo
Az IsolatedFileStream osztály
Irás az IsolatedStorege-be
Olvasás az IsolatedStorage-ból
Az IsolatedStorage megkeresése
A NetworkStream osztály
File olvasás és írás osztályok
FileSystemInfo navigáció és utilities
File attributumok
File és Directory, valamint Path Utility osztályok
Encrypt és Decrypt
Open és Read operációk]
FileSystemWatcher
File writing and reading
StreamReader/Writer
BufferedStream

A központ és a külvilág.

Már assembly tanulmányaink során láttuk, hogy a processzor rendelkezett néhány olyan utasítással, melyekkel a külvilággal adatot cserél. Ezek az adatcserék persze igen korlátozottak voltak, és általában processzor regiszterméretéhez igazított nagyságú elemi méretek voltak alkalmazhatók. A magas szintű nyelvekben a program és a külvilág, vagy a program egy adattárolójának és a külvilágnak az adatcseréje szinten gyakori igény, sőt olyan mértékben gyakori, hogy a nyelvek fejlesztői igyekeztek a lehetőségek szerinti legabsztraktabb módon kezelni (vagyis általános, többcélú, szabványos, támogatással) az adatok átvitelét.

6-1. ábra -

kepek3/11fejezet-6-1.jpg


A Stream fogalma

Az első megközelítésben a stream-et (melynek jelentése ár, áradat, folyam stb.) egy olyan objektumnak tekintsük, melyben az adatok áramlása valósul meg. Az áramlás mindig egy forrás és egy nyelő között zajlik, vagyis mindig van iránya. Lényege az általánosításnak, hogy az adat jellege a stream originális értelmezésében közömbös. Ez azt jelenti, hogy az egyes megvalósítások specializálják az adatáramlás adattípusát, valamint a forrás, és a nyelő jellegét.

Az eldobható osztály

Most, hogy már van általános képünk az adatok „folyamszerű” átviteléről, fontos, hogy az operációink során használt erőforrásaink felszabadítását is ismerjük, hiszen az alkalmazott megoldás költségeinek alacsony szinten tartása az adott esetben nagymennyiségű adat transzfere során létfontosságú. Ezért, mielőtt továbbhaladnánk, nézzük meg annak a technikáját, hogy hogyan lehet az erőforrások felszabadítását optimálisan időzíteni a használat végére.

Tekintsük az alábbi példa osztály kódját:

Mint látjuk, az osztály implementálja az IDisposable interface-t, melyben az osztály által használt erőforrásokat magunk szabadítjuk fel (pld. Konnekció). Ha nyomkövetjük az alábbi kódot, láthatjuk, hogy a using direktíva használatával az osztály a záró kódblokk után automatikusan felszabadítja az erőforrásait. (úgy, hogy meghívja a Dispose metódust)

A Stream alaposztályának definíciója szerint megvalósítja a fenti példában implementált IDisposable interface-t, vagyis a using használatával kezelhetjük az adatfolyam erőforrásainak életciklusát. A megoldás teljesen egyenértékű azzal, amikor egy try-catch blokk finally metódusában közvetlenül meghívjuk a try blokkban igénybe vett objektumok Dispose metódusát, hiszen, mint tudjuk, a finnaly ág biztosan végrehajtódik.

6-2. ábra - http://msdn.microsoft.com/en-us/library/system.io.stream(v=vs.110).aspx

kepek3/11fejezet-6-2.jpg


Mint láthatjuk, a Stream osztály a System.IO.Stream névtérben van, és az alábbi tulajdonságokkal rendelkezik az msdn szerinti definícióban:

6-3. ábra -

kepek3/11fejezet-6-3.jpg


A használat szempontjából az absztrakt ősosztály „legfontosabb” tulajdonságai az alábbiak.

bool CanRead

Olvasható flag

bool CanSeek

Témogatott az aktuális pozíció beállítás vagy nem

bool CanWrite

Irható flag

long Length

A stream adatfolyaműnak hossza

long Position

Az aktuális stream adatpozició (irható/olvasható)

A stream-ek általánosan rendelkeznek néhány alapvető metódussal, melyet a leszármazó osztályok is örökölnek.

void Close()

Lezárja a kapcsolatot

void Flush()

Kiírja az adatokat a kimenetre

int ReadByte()

Visszatér egy byte méretű adattal

int Read(byte[ ] buf,int offset, int numBytes)

Bufferbe ír az adott offset-től adott számú byte-ot

long Seek(long offset,SeekOrigin origin)

Beállítja az aktuális poziciót az adott irányban az offsetre

void WriteByte(byte b)

Egy byte kiírása a kimentre

void Write(byte[ ] buf,int offset, int numBytes)

Kiírás egy részhalmazra adott offsettől adott byte-ra

Fenti rövid bevezetés után lássuk a stream-ek széles felhasználási lehetőségeinek specifikus példányait. Ebben a projektben az alábbi névterekre támaszkodunk.

A FileStream osztály

Specikusan a háttértárakon tárolt állományok soros olvasására tervezett osztály, melynek alkalmazása nagyon gyakori. Általában az állományok létrehozására, kiírására és visszaolvasására használjuk. Nézzünk rögtön egy kódolt példát.

6-4. ábra -

kepek3/11fejezet-6-4.jpg


A fenti program valójában két részből áll, egy fájl-létrehozási/írási, és egy beolvasási/kiírási részből. Érdekes megfigyelni, hogy a fenti program a visszaolvasási részben ad egy bell hangot is a neki megfelelő karakterkód (7-es) vételekor, vagyis a stream minden, a fájlban tárolt karaktert bájt-sorrendben olvas, illetve ír.

A MemoryStream osztály I.

Látható, hogy az első lépésben az „Ez egy test szöveg amit beírunk a memory stream-ba.” literált bájtonként beírjuk egy bájt-tömbbe, majd a fájl nevében nem megengedhető karakterekkel töltünk fel egy másik tömböt.

6-5. ábra -

kepek3/11fejezet-6-5.jpg


Az első buffer tartalmát a lefoglalt memória-területre írjuk egy lépésben, majd a második buffer tartalmát folytatólagosan ugyanoda, de bájtonként. Ezt követően lekérjük az „allokált” terület állapotának értékeit kapacitás, hossz és aktuális pozíció sorrendben. Végül a memóriából visszaolvassuk a korábban beleírt tartalmat, szintén két lépésben, egy 20-bájtos blokkban, illetve bájtonként, majd dekódolás után a képernyőn megjelenítjük.

6-6. ábra -

kepek3/11fejezet-6-6.jpg


A memória-stream nagyon hasznos, ha valami alacsony szintű vagy különlegesen gyors algoritmust szeretnénk futtatni a memóriában lévő adatokon. Erre mutat példát következő feladat.

A MemoryStream osztály II.

Ebben a feldatban először feltöltünk egy buffert véletlen számokkal, majd egy MemoryStream használatával egy másik bufferbe az eredeti buffer tartalmát úgy másoljuk át, hogy minden 2,4,6,8 helyére asc II 0-át azaz 48-at írunk.

6-7. ábra -

kepek3/11fejezet-6-7.jpg


A CryptoStream osztály

Manapság különösen fontosak az adatok rejtésének technikái, hiszen például az interneten vagy akár a zárt hálózaton is áramolhatnak olyan adatok, melyek bizalmas voltuk miatt védelmet igényelnek.

Advanced Encryption Standard (AES) - régen volt a DES (lucifer). A Rijndael titkosítási eljárást, mint Advanced Encryption Standardot az USA Szabványügyi Intézete (NIST) 2001-ben fogadta el, lecserélve ezzel az addigi, már elavult titkosítási eljárást, a DES-t. Az AES kiválasztását széleskörben meghirdetett verseny előzte meg.

A NIST olyan szimmetrikus kulcsú blokk-kódolót keresett, amely 128 bites adatblokkok kódolására képes, és ehhez háromféle kulcsméret használatát teszti lehetővé: 128, 192 és 256 biteset. A kiválasztás szempontjai voltak: a kicsi méret, a nehéz törhetőség, a gyorsaság és a kis eszközökben való alkalmazhatóság. 1999 augusztusában a kiválasztási verseny második fordulójában mindössze öt algoritmus maradt: a MARS, az RC6TM, a Rijndael, a Serpent és a Twofish. A győztés végül a Rijndael lett, amely eredeti nevét kitalálóiról (Vincent Rijmen és Joan Daemen) kapta – továbbiakban ezt nevezzük AES-nek. Az AES kódoló nem a „klasszikus” Feistel-struktúrát követi (mint például a DES), itt a kódolást és dekódolást különböző eljárások végzik. A kódolás négy különböző transzformáció többszöri megismétlése, míg a dekódolás az egyes transzformációk inverzének megfelelő sorrendben történő végrehajtása. Az AES a kódolásra van optimalizálva, aminek indoka, hogy több kódoló struktúra (CFB, OFB) csak ezt a módot használja.

Az encriptálást megvalósító függvény pedig a következő:

6-8. ábra -

kepek3/11fejezet-6-8.jpg


6-9. ábra -

kepek3/11fejezet-6-9.jpg


A GZipStream osztály

A tömörítés, mellyel veszteségmentesen kisebb helyigényű állományokat hozhatunk létre, szintén alapvetően fontos a hálozati kommunikáció körében, hiszen egy-egy nagy állomány átvitele sok időt vehet igénybe. Ebben a példában mutatjuk meg, hogy a stream-ek egymásban ágyazva is működhetnek, sőt hála az eredeti absztrakt tervezésnek, ez célszerű használatuk rendje. Gyakori alkalmazás például webszolgáltatások esetén, hogy az enkriptált (tehát rejtett) tartalmat deflate eljárással (vagyis veszteségmentesen) tömörítjük, és a szolgáltatás mindezt a szerveroldalon elvégezve küldi ki a csomagot. A fogadó oldalon a kitömörítés után a dekriptálás csak a jogosultnak sikerül, így a kommunikáció egyszerre hatékony és biztonságos is.

A fenti példában egy FileStream-et foglalunk egy GZipStream-ba melynek futási képe az alábbi:

6-10. ábra -

kepek3/11fejezet-6-10.jpg


Eközben a fájlrendszerben létrehozott tömörített állomány:

6-11. ábra -

kepek3/11fejezet-6-11.jpg


A tömörített állomány bináris tartalma a következő:

6-12. ábra -

kepek3/11fejezet-6-12.jpg


A Deflate compression

Mint a GZip, de kisebb, mert nem tartalmazza a utility-khez szükséges járulékos információkat. A programozó saját céljaira kiválóan megfelel. Itt is egymásban ágyazott stream-eket használunk.

6-13. ábra -

kepek3/11fejezet-6-13.jpg


6-14. ábra -

kepek3/11fejezet-6-14.jpg


System Environment demo

Talán szorosan nem ide illik, de nagyon hasznos rendszerünk környezeti paramétereinek ismerete, sőt azoknak a programból történő elérése. Az alábbi ki programrészletben erre mutatunk példát.

6-15. ábra -

kepek3/11fejezet-6-15.jpg


Az IsolatedFileStream osztály

A .Net-ben a program állapot-jellemzőinek korlátozott jogokkal történő mentésére vezették be az Isolated Storage koncepciót. Isolated Storage tulajdonképpen egy virtuális alkönyvtár, ahol a fájlok tárolódnak. A felhasználó nem tudja, hogy hol van tulajdonképpen, de nincs is rá szüksége. A felhasználó csak arról értesíti a .Net-et, hogy Isolated Storage-t akar használni. A tárolás fizikai helye minden operációs rendszerben más és más lehet. Az alkalmazás az Isolated Storage osztályok használatával egyszerűen hozzáfér az állapot adataihoz, illetve képes ilyen állományokat létrehozni. Az Isolated Storage (statikus) eljárások vagy Assembly (közös), vagy felhasználó szerint szeparáltak. Elemei az IsolatedStorageFile és az IsolatedStorageStream.

Irás az IsolatedStorege-be

Mint korábban láttuk, a Stream

Writer lezárja a bennfoglalt IsolatedStorageStream-et.

Olvasás az IsolatedStorage-ból

6-16. ábra -

kepek3/11fejezet-6-16.jpg


Az IsolatedStorage megkeresése

Bár a program szerzőjének nem kell tudnia, hogy közvetlenül hol helyezkedik el az izolált fájl rendszere, most mégis közre adunk egy kis labor-gyakorlatot, és megnézzük annak helyét.

6-17. ábra -

kepek3/11fejezet-6-17.jpg


Miközben az ISFConfig.txt állomány az alábbi helyen található:

6-18. ábra -

kepek3/11fejezet-6-18.jpg


ahol valóban létezik…

6-19. ábra -

kepek3/11fejezet-6-19.jpg


A NetworkStream osztály

Ez példa egy hálozatos adatátvitelt ismertet, melyben a kliens és a server oldal között történik az adatcsere természetesen stream-ek felhasználásával.

A működési szerepet a checkbox állapota határozza meg az alábbi eseménykezelés szerint:

6-20. ábra -

kepek3/11fejezet-6-20.jpg


Valójában a nyomógomb kétfunkciójú esetszétválasztott kódjában történik a kommunikáció az alábbi küldés és fogadás szerint.

Mint látható, az üzenet lehet kódolt vagy kódolatlan, illetve a fogadás szintén. A megoldás a korábban ismertett enkriptált, illetve dekriptált algoritmusokat használja az előzetesen felvett változók használatával.

File olvasás és írás osztályok

Láttuk, hogy a filestream alkalmazásával képesek vagyunk a fájlrenndszerben tárolt állományok írására és olvasására, de egy következő specializációs szint rendelkezésre állásával kiterjedt egyéb műveleteket is végezhetünk. Az alábbi projektben megvizsgáljuk, és teszteljük a System.IO névtérnek a fájlrendszert támogató osztályait a fenti projekt-tartalom alapján.

6-21. ábra -

kepek3/11fejezet-6-21.jpg


FileSystemInfo navigáció és utilities

Ebben a kódban meghívunk egy alább ismertetett rekurzív elvű függvényt, mellyel az alkönyvtárak szerkezetét „derítjük fel”. Ezekben az alkönyvtárakban található .cs kiterjesztésű állományokat egy StreamReader-rel beolvassuk, és „megszámláljuk” az érvényes sorokat.

6-22. ábra -

kepek3/11fejezet-6-22.jpg


Ez tipikusan egy olyan feladat, melyben a rekurzív hívásra szükség van, sőt enélkül igencsak nehéz lenne az előre nem látható mélységű alkönyvtárakat felderíteni. Ha egy nagyobb feldatot adunk ennek a kódrészletnek, például határozzuk meg az ITMagister projektben a cs-ben írt kód sorainak számát akkor a következő ereményt kapjuk:

6-23. ábra -

kepek3/11fejezet-6-23.jpg


Vagyis a kiragadott alkönyvtárban a .cs kiterjesztésű állományok sorainak száma 20985 db.

6-24. ábra -

kepek3/11fejezet-6-24.jpg


6-25. ábra -

kepek3/11fejezet-6-25.jpg


File attributumok

A System.IO névtérben lévő File osztály tartalmaz néhány tagfüggvényt, mellyel a fájlrendszerben szokásos néhány hasznos operáció elvégezhető. Ezek tekintjük át a következőkben.

http://msdn.microsoft.com/en-us/library/System.IO.File(v=vs.110).aspx

A mellékelt kódrészletben létrehozunk egy Guid filenevű állományt, majd a StreamWriter segítségével beleírunk, illetve lementjük.

Ezt követően readonly-ra állítjuk az attributumát majd töröljük úgy, hogy a read only attributumot feloldjuk.

File és Directory, valamint Path Utility osztályok

A fájlrendszer alkönyvtár szerkezetének kezelése, bővítése, törlése szintén mindennapos feladat lehet. Lássunk ezekre egy-egy példát.

A mellékelt kódrészletben létrehozunk egy fájlt, ha még nem létezik, majd a StreamWriter osztállyal beleírunk néhány sort.

Encrypt és Decrypt

Ebben a névtérben használhatjuk az encriptálás és a decriptálás metódusokat a mellékelt kódrészlet szerint, ahol a path változóben rejtett filenév a korábban létrehozott MyTest.txt.

6-26. ábra - http://msdn.microsoft.com/en-us/library/system.io.file.encrypt(v=vs.110).aspx

kepek3/11fejezet-6-26.jpg


Open és Read operációk]

Végül megnyitjuk, és visszaolvassuk, illetve kiírjuk a StreamReader segítségével a korábban manipulált állományt, majd másoljuk és töröljük.

6-27. ábra -

kepek3/11fejezet-6-27.jpg


FileSystemWatcher

Helyet kapott itt még egy igen hasznos szolgáltatás ismertetése, mellyel a fájlrendszerben történt változásokról kaphatunk visszajelzést.

Mint a mellékelt kódrészletből látszik, egy adot path-lal kapcsolatos eseményekről kaphatunk információt, ha feliratkozunk a megfelelő eseményekre, melyeket a hozzájuk tartozó eseménykezelőben kezelhetünk.

6-28. ábra -

kepek3/11fejezet-6-28.jpg


File writing and reading

A klasszikusnak mondható fájl műveletekről pedig az alábbi kódrészletben találhatunk példát.

Ebben a kódrészletben érdemes észrevenni, hogy mód van az adott fájlt a beírás idejére zárolni a lock metódussal. Ezzel a konkurens írási operációkat zárhatjuk ki a kódban betartott szabályok konzekvens alkalmazásával, vagyis az unlock használatával.

6-29. ábra -

kepek3/11fejezet-6-29.jpg


StreamReader/Writer

A stream-ek egymásba ágyazásának technikáját mutatja be az alábbi két utolsó példa:

6-30. ábra -

kepek3/11fejezet-6-30.jpg


BufferedStream

Végül a BufferedStream alkalmazására vegyünk egy példát, melyben a StreamWriterrel írunk a fájlba, miközben az egymásba ágyazott stream-eken keresztül folyik az operáció.

6-31. ábra -

kepek3/11fejezet-6-31.jpg