Streams i .NET

Tags:    .net
Skrevet af Bruger #4522 @ 20.04.2008

Et FileStream-eksempel: Synkront I/O


Følgende program illustrerer synkront I/O:

Fold kodeboks ind/udKode 


Først laver vi et FileStream-objekt for filen "Hemmeligheder.txt". Vi angiver FileMode.OpenOrCreate så hvis der allerede er en fil med det navn åbnes den ellers oprettes en ny fil med navnet. Som også illustreret i eksemplet skal alt som vi ønsker at skrive først konverteres til bytes da stream-klasserne som tidligere nævnt kun er byte-orienterede og altså kun kender til I/O med bytes. Når jeg koder en tekst som bytes skal jeg vælge en såkaldt "kodning", og her har jeg valgt UTF8 som er ganske kendt - jeg kunne også havde brugt ASCII. Under alle omstændigheder er det selvklart meget vigtigt at man anvender samme kodning når man læser teksten tilbage som man anvendte til teksten da denne blev forberedt til skrivning, for ellers bliver resultatet noget volapyk; så når jeg til sidst i programmet læser tilbage hvad jeg skrev, sørger jeg for at benytte samme UTF8-kodning. Bemærk også at man skal huske at flytte ens position tilbage til filens begyndelse når man skal læse fra den.

Følgende skærmbillede viser kørsel af ovenstående lille program:



Et andet FileStream-eksempel: Asynkront I/O


Næste kodeboks illustrerer asynkront I/O:

Fold kodeboks ind/udKode 


I dette program er vores FileStream-objekt en statisk variabel i klassen. Lige så er den buffer der holder det vi læser fra filen. Jeg opretter min buffer med en størrelse på 64 bytes - det er bare et tilfældigt valgt tal, og i et rigtigt program vil det afhænge af flere faktorer. Jeg opretter også en AsyncCallback delegate kaldet Callback som jeg initierer til min statiske metode FinishedReading. I Main påbegynder jeg den asynkrone læsning fra filen og jeg bruger den delegate jeg oprettede som argument i BeginRead. Alt dette betyder at den statiske metode FinishedReading bliver kaldt når bufferen er fyldt op. I FinishedReading starter jeg med at tjekke om den netop overståede læsning overhovedet returnerede noget, og hvis det er tilfældet udskrives det, og jeg påbegynder en ny læsning (bare fordi min buffer er fuld kan der jo sagtens være mere at hente fra datastrømmen). Bemærk også at jeg i FinishedReading husker at kalde EndRead på filstrømmen som det første. Det er vigtigt så glem det nu ikke!

I Main simuleres et langkørende program med en for-løkke og nogle Thread.Sleep()-metodekald. Hvis du kører dette program så sørg for at den fil du bruger indeholder rigtig meget tekst, på den måde kommer alle de mange Thread.Sleep()-kald til deres ret, og FinishedReading vil blive kaldt rigtig mange gange, hver gang udskrivende det netop læste.

Lager-stream nummer to: MemoryStream


Jeg vil ikke bruge så meget tid på MemoryStream, blot sige at den er velegnet til de situationer hvor du har brug for hurtig tilgang til data der bruges ofte i et program. Som navnet antyder holder MemoryStream dens data i computerens hukommelse hvilket gør at der er meget hurtigere at få fat i end hvis f.eks. vi havde det i en fil og skulle bruge FileStream. MemoryStream kan f.eks. bruges til at holde serialiserede objekter i hukommelsen.

Lager-stream tre: NetworkStream


Vi kan sende data gennem såkaldte netværks-sockets i .NET ved at bruge NetworkStream-klassen. Bemærk at denne klasse ikke residerer i namespace System.IO men i System.Net.Sockets.

Ikke overraskende kan man ikke seeke i en netværksstrøm, og hvis man ønsker bruge en buffer skal man huske at bruge BufferedStream-dekotratørstrømmen (se nedenfor for mere om den).

Følgende properties er nævneværdige:



DataAvailable kan bruges til at finde ud af om der er data tilgængelig i netværksstrømmen (der returneres sand såfremt det er tilfældet). Readable kan fortælle dig om du har læserettigheder til strømmen. Socket returnerer et objekt af typen Socket som er den underliggende netværks-socket som kommunikationen foregår igennem. Hvis du har skriverettigheder på strømmen returnerer Writeable sand.

Bemærk at den property der hedder Position som blev diskuteret ovenfor under gennemgangen af den abstrakte Stream-klasse ikke står nævnt her, da NetworkStream som nævnt ikke understøtter seeking hvorfor Position ikke kan bruges på en NetrowkStream (hvis du forsøger kastes en undtagelse).

For at oprette en NetworkStream skal du først have fingrene i en socket, men vi vil i stedet se hvordan man får fat i en NetworkStream via en TcpClient.

Vi vil nemlig nu lave en meget simpel TCP server og en simpel TCP klient som bruger en NetworkStream til at kommunikere med hinanden.

Følgende kodeboks viser koden for vores lille og enkle TCP server:

Fold kodeboks ind/udKode 


Det første vi gør er at lave en variabel der holder den port vi ønsker at kommunikere igennem - jeg har her helt tilfældigt valgt port 5555. Derefter opretter jeg et IPAddress-objekt der repræsentere den IP-adresse som vi ønsker vores server skal lytte på og her vælger jeg bare localhost adressen så jeg kan køre sever og klient på samme maskine. Disse to ting (min port og min IP adresse) bruger jeg dernæst til at oprette et TcpListener-objekt.

Et TcpListener-objekt er det objekt som vi bruger til at lytte efter klienters TCP-forespørgelser, og vi starter serveren ved at kalde dens Start-metode. I denne simple server tilbyder vi kun simpel synkron kommunikation, og det opnår vi ved at kalde metoden AcceptTcpClient som næste trin. Denne metode blokerer indtil der er en klient der ønsker at kommunikere med os. Når det sker, kommer vi endelig til det som det hele handler om - nemlig vores NetworkStream som vi får fat i ved at kalder GetStream-metoden på det klientobjekt vi fik fra AcceptTcpClient-metoden. Herefter er der "business as usual": vi læser de første 100 bytes fra netværksstrømmen og ekkoer dem blot ud til konsollen så vi kan verificerer at serveren (og klienten) virker efter hensigten. Til sidst lukker vi først vores NetworkStream og derefter vores TcpListener.

Og nu til klienten:

Fold kodeboks ind/udKode 


Vi starter med at lave et TcpClient-objekt som vi kan bruge til at kommunikere med en TCP server i den anden ende. Derefter kalder vi Connect-metoden hvor vi som argumenter angiver den port og IP-adresse som vi ønsker at forbinde til.

Resten af programmet minder lidt om serveren, bortset fra at vi her skriver til vores NetworkStream-objekt i stedet for at læse fra det. Vi starter med at kalde GetStream på vores TcpClient-objekt for at få fingrene fat i den datastrøm som vi skal bruge til netværkskommunikationen. Den tekst vi ønsker at sende igennem nettet koder vi i nogle bytes som vi gemmer i en byte-tabel. Selve kommunikationen sker når vi så kalder Write-metoden på vores NetworkStream-objekt. Vi slutter af med at kalde Close på både datastrømmen som klienten.

Når serveren startes vil konsolvinduet være tomt, mens den står og venter på "besøgende". Når vi så starter vores klient, som jo sender data til severen, ekkoer serveren blot beskeden til konsolvinduet, og resultatet bliver så:





Hvad synes du om denne artikel? Giv din mening til kende ved at stemme via pilene til venstre og/eller lægge en kommentar herunder.

Del også gerne artiklen med dine Facebook venner:  

Kommentarer (2)

User
Bruger #6559 @ 20.04.08 11:17
En meget god artikel. Jeg synes at når du begynder at skrive om MemoryStream, så er det synd du ikke går mere i dybden med det når du alligevel begynder at fortælle lidt om klassen. Der burde du måske bare have undværet det helt og så skrevet i starten at det ikke var med i artiklen. Ellers faldt jeg ikke over det helt store. Godt beskrivende og helt sikkert noget jeg kommer til at bruge. Keep up the good work :)
User
Bruger #4522 @ 20.04.08 15:54
Tak for dine kommentarer Martin. Du har en pointe omkring MemoryStream og jeg vil prøve at uddybe det lidt mere i artiklen.
Du skal være logget ind for at skrive en kommentar.
t