15
Tags:
.net
Skrevet af
Bruger #4522
@ 20.04.2008
Et FileStream-eksempel: Synkront I/O
Følgende program illustrerer synkront I/O:
using System;
using System.IO;
using System.Text;
namespace Synchronous
{
class Program
{
static void Main()
{
// Let's create a FileStream - by default we have synchronous I/O
FileStream fs = new FileStream("Hemmeligheder.txt", FileMode.OpenOrCreate);
//
// Let's start by writing a secret message to the stream...
//
// Let's write some bytes
Console.WriteLine("WRITING A SINGLE BYTE!");
fs.WriteByte(Convert.ToByte('X'));
// Let's write a whole array of bytes!!!
Console.WriteLine("NOW WRITING MANY BYTES IN ONE GO!!!!!");
byte[] toBeWritten = Encoding.UTF8.GetBytes(" marks the spot. Now GO!");
fs.Write(toBeWritten, 0, toBeWritten.Length);
//
// Ok. Let's read that back and print it out to the console.
//
// First, we se the point back to the beginning of the file.
// Remember the Seek() method from the article?
fs.Seek(0, SeekOrigin.Begin);
Console.WriteLine("NOW READING WHAT WAS WRITTEN TO THE FILE!!!!");
byte[] bytesReadFromFile = new byte[fs.Length];
fs.Read(bytesReadFromFile, 0, bytesReadFromFile.Length);
Console.WriteLine("This was in the file: {0}",
Encoding.UTF8.GetString(bytesReadFromFile));
}
}
}
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:
using System;
using System.IO;
using System.Text;
using System.Threading;
namespace Asynchronous
{
public class Program
{
private static readonly FileStream fs =
new FileStream(@"BigNovel.txt", FileMode.Open, FileAccess.Read, FileShare.Read, 64, true);
private static readonly byte[] bytesFromStream = new byte[64];
private static readonly AsyncCallback Callback = FinishedReading;
// This is called when the buffer is full
private static void FinishedReading(IAsyncResult ar)
{
int bytesRead = fs.EndRead(ar);
if(bytesRead>0)
{
Console.WriteLine(Encoding.ASCII.GetString(bytesFromStream, 0, bytesRead));
// We try to read aging - maybe there's more...
fs.BeginRead(bytesFromStream, 0, bytesFromStream.Length, Callback, null);
}
}
public static void Main(string[] args)
{
fs.BeginRead(bytesFromStream, 0, bytesFromStream.Length, Callback, null);
// This simulates a long running program...
for(long i = 0; i < 300000; ++i)
{
if(i % 500 == 0)
{
Thread.Sleep(5);
}
}
fs.Close();
}
}
}
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:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace NetworkStreamExampleTcpListener
{
public class TcpListenerExample
{
public static void Main()
{
try
{
// First we create the listener on port 5555 at the localhost address
int port = 5555;
IPAddress localHost = IPAddress.Parse("127.0.0.1");
TcpListener tcpListener = new TcpListener(localHost, port);
// Now we enter the state were we listen for request
tcpListener.Start();
// Here we create the client that represents the incomming request.
// The AcceptTcpClient is a blocking method that returns the requesting
// TcpClient, i.e. we have synchronous communication here.
TcpClient client = tcpListener.AcceptTcpClient();
NetworkStream ns = client.GetStream();
// Now we read form the request
byte[] bytesFromClient = new byte[100];
// We read 100 bytes
ns.Read(bytesFromClient, 0, 100);
// Print the read data to the console
Console.WriteLine(Encoding.ASCII.GetString(bytesFromClient));
ns.Close();
tcpListener.Stop();
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
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:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
namespace NetworkStreamExampleTcpClient
{
public class TcpClientExample
{
public static void Main(string[] args)
{
try
{
// We create a client and connect to the port on which our listener lives,
// i.e. port 5555 on the localhost IP address
TcpClient client = new TcpClient();
int port = 5555;
IPAddress localHost = IPAddress.Parse("127.0.0.1");
client.Connect(localHost, port);
// Get the stream for the communication
NetworkStream st = client.GetStream();
// Build the message to send to the server, using ACII encoding
// (which the server expects)
byte[] bytesToSend = Encoding.ASCII.GetBytes("Hello listener, I'm a client from hell!!");
st.Write(bytesToSend, 0, bytesToSend.Length);
st.Close();
client.Close();
}
catch(Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
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)
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
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.