Working with Streams

Streams are classes that specialize in data transfer. You use them to read and write data to and from files, networks, memory, strings, and the Internet. Here's an overview of the common stream classes in C#:

  • Stream Abstract class for working with bytes in streams; the base class for streams.

  • BinaryReader / BinaryWriter Reads and writes data in binary format.

  • BufferedStream Adds buffering to another stream.

  • MemoryStream Works with data in memory directly.

  • FileStream Reads and writes files. Supports synchronous and asynchronous random access to files.

  • TextReader / TextWriter Abstract text reader and writer classes that use Unicode.

  • StringReader / StringWriter Reads from and writes to text strings using streams.

  • NetworkStream Stream that works with a network connection.

You can determine the capability of a stream using the CanRead , CanWrite , and CanSeek properties. For streams that support seeking, use the Seek method and the Position property to get or set your current position in the stream, and the Length property to get the length of a stream. To write data to a stream, you use the Write method; to read data from a stream, you use the Read method.

You open a stream with the stream class's constructor; calling Close closes the stream. Some streams perform buffering of the underlying data to improve performance. For those streams, you can use the Flush method to clear any internal buffers and make sure that all data has been written out; the Close method also flushes internal data buffers. Usually, you're responsible for setting up your own data buffers, but the BufferedStream class provides the capability of wrapping a buffered stream around another stream in order to improve read and write performance.

Let's get to some examples. The basic, low-level stream for working with files is FileStream , so we'll start with that.

Reading and Writing Binary Files

The FileStream class lets you treat files in binary format, as simple bytes. Using the Read and Write methods of this class, you can read bytes from a file and write a set number of bytes out to a file. This class treats its data as a simple byte stream, without interpreting that data, as the text streams we'll see in a few pages do. You can see the significant public properties of FileStream in Table 5.7, and the significant public methods of that class in Table 5.8.

Table 5.7. Significant Public FileStream Properties

PROPERTY

PURPOSE

CanRead

Returns true if the current stream supports reading.

CanSeek

Returns true if the current stream supports seeking.

CanWrite

Returns true if the current stream supports writing.

Length

Returns the length in bytes of the stream.

Name

Returns the name of the file stream that was passed to the constructor.

Position

Returns or sets the current position of this stream.

Table 5.8. Significant Public FileStream Methods

METHOD

PURPOSE

BeginRead

Begins an asynchronous read.

BeginWrite

Begins an asynchronous write.

Close

Closes the file.

EndRead

Waits for the pending asynchronous read to finish.

EndWrite

Ends an asynchronous write, blocking (that is, not returning) until the I/O operation has completed.

Flush

Clears all buffers for this stream and causes any buffered data to be written out.

Lock

Stops other processes from changing the file stream (but permits read access).

Read

Reads a block of bytes and writes the data into a given buffer.

ReadByte

Reads a byte and advances the read position one byte.

Seek

Sets the current position of this stream to the given location.

SetLength

Sets the length of this stream to the given value.

Unlock

Allows access by other processes to a file that had been locked.

Write

Writes a block of bytes to this stream from a buffer.

WriteByte

Writes a byte to the current position in the file stream.

The best way to understand streams is to see them in action. Here's an example, ch05_04.cs, which treats its own source code as a binary file, reads it into a buffer, and writes that buffer out to a file named ch05_04.bak, providing you with a backup copy of this application's source code. This example uses the FileStream class's constructor to open files for reading and writing. As with other streams, there are various overloaded forms of this stream's constructor; here's the one we'll use:

 
 FileStream(string,  FileMode, FileAccess, FileShare  ); 

Here are the members of the FileMode enumeration, indicating how you want to open the file:

  • FileMode.Append

  • FileMode.Create

  • FileMode.CreateNew

  • FileMode.Open

  • FileMode.OpenOrCreate

  • FileMode.Truncate

Here are the members of the FileAccess enumeration, indicating what you want to do with the file:

  • FileAccess.Read

  • FileAccess.ReadWrite

  • FileAccess.Write

And here are the members of the FileShare enumeration, indicating how you want other processes to be able to work with the file at the same time:

  • FileShare.Inheritable

  • FileShare.None

  • FileShare.Read

  • FileShare.ReadWrite

  • FileShare.Write

Here's how we use the FileStream constructor to open ch05_04.cs for reading, and ch05_04.bak for writing (this code will create ch05_04.bak if necessary, or if that file exists, open it and truncate it to zero length):

 
 FileStream input = new FileStream("ch05_04.cs",   FileMode.Open, FileAccess.Read, FileShare.None); FileStream output = new FileStream("ch05_04.bak",   FileMode.OpenOrCreate, FileAccess.Write, FileShare.None); FileStream output = File.OpenWrite("ch05_04.bak"); 

We'll use the FileStream Read method to read data from the source file into a byte array buffer and the Write method to write that data to the target file. You pass the Read method the buffer to use for data, the offset in that buffer to store data at, and the length of the buffer. This method returns the number of bytes it's read, so we can keep looping until it runs out of data to read. The Write method writes data to the backup file; you pass it the data buffer, the offset in the buffer where your data starts, and the number of bytes to write. You can see this at work in ch05_04.cs, Listing 5.4.

USING FILE OR FILESTREAM METHODS

As with many stream techniques, there are many ways to open these files. Not only can you use other (and simpler) FileStream constructors, but you can also use the File class's OpenRead and OpenWrite methods to get FileStream objects. That is, the previous code performs the same action as this code: FileStream input = File.OpenRead("ch05_04.cs"); FileStream output = File.OpenWrite("ch05_04.bak"); .


Listing 5.4 Opening and Writing Binary Files (ch05_04.cs)
 using System.IO; class ch05_04 {   public static void Main()   {  int numberBytes;   byte[] dataBuffer = new System.Byte[4096];  FileStream input = new FileStream("ch05_04.cs",       FileMode.Open, FileAccess.Read, FileShare.None);     FileStream output = new FileStream("ch05_04.bak",       FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);  while ((numberBytes = input.Read(dataBuffer, 0, 4096)) > 0)   {   output.Write(dataBuffer, 0, numberBytes);   }   input.Close();   output.Close();  } } 

When you run ch05_04, it opens its own source code, ch05_04.cs, and copies that code over, byte-by-byte, to ch05_04.bak.

Note that file handling is one of the most exception-prone things you can do in programming, so in general you should enclose your file-handling operations in a try / catch block like this:

 
 using System.IO; class ch05_04 {   public static void Main()   {     int numberBytes;     byte[] dataBuffer = new System.Byte[4096];  try   {  FileStream input = new FileStream("ch05_04.cs",         FileMode.Open, FileAccess.Read, FileShare.None);       FileStream output = new FileStream("ch05_04.bak",         FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);       while ((numberBytes = input.Read(dataBuffer, 0, 4096)) > 0)       {         output.Write(dataBuffer, 0, numberBytes);       }       input.Close();       output.Close();  }   catch(System.Exception e)   {   System.Console.WriteLine(e.Message);   }  } } 

You can find which exceptions file-handling operations throw in the C# documentation; for example, the FileStream constructor can throw ArgumentException , ArgumentNullException , FileNotFoundException , IOException , and DirectoryNotFoundException exceptions. Although we're going to omit try / catch handling in this chapter for the sake of brevity, bear in mind that you should use it in general when working with files in a production environment.

Creating Buffered Streams

In the previous example, we created our own buffer to read and write data with. On the other hand, it turns out that sometimes, larger or smaller buffer sizes can improve file-handling performance. For that reason, you can wrap a FileStream object in a BufferedStream object, which will use its own internal buffer to maximize performance. All you have to do is to pass the FileStream object to the BufferedStream constructor and then use the returned BufferedStream object from then on. You can see this in action in ch05_05.cs, Listing 5.5.

Listing 5.5 Buffered File I/O (ch05_05.cs)
 using System.IO; class ch05_05 {   public static void Main()   {     int numberBytes;     byte[] dataBuffer = new System.Byte[4096];     FileStream input = new FileStream("ch05_05.cs",       FileMode.Open, FileAccess.Read, FileShare.None);     FileStream output = new FileStream("ch05_05.bak",       FileMode.OpenOrCreate, FileAccess.Write, FileShare.None);  BufferedStream bufferedInput = new BufferedStream(input);   BufferedStream bufferedOutput = new BufferedStream(output);   while ((numberBytes = bufferedInput.Read(dataBuffer, 0, 4096)) > 0)   {   bufferedOutput.Write(dataBuffer, 0, numberBytes);   }   bufferedOutput.Flush();   bufferedInput.Close();   bufferedOutput.Close();  } } 

Reading and Writing Text Files

C# also supports the StreamReader and StreamWriter classes for working with text files. Text files are binary files like any other, but their data is Unicode text arranged into lines (in C#, a line is a sequence of characters followed by a line feed [ "\n" ] or a carriage return immediately followed by a line feed [ "\r\n" ]). What's special about the StreamReader and StreamWriter classes is that they offer the ReadLine and WriteLine methods that let you handle your data in a line-oriented way. You can see the significant public methods of the StreamWriter class in Table 5.9, and the significant public methods of the StreamReader class in Table 5.10.

Table 5.9. Significant Public StreamWriter Methods

METHOD

PURPOSE

BeginRead

Begins an asynchronous read.

Close

Closes the stream.

Flush

Clears all buffers for the current writer.

Write

Writes to the stream.

WriteLine

Writes data followed by a line terminator.

Table 5.10. Significant Public StreamReader Methods

METHOD

PURPOSE

Close

Closes the stream.

Peek

Returns the next available character but does not treat it as having been read.

Read

Reads the next character or next set of characters from the input stream.

ReadBlock

Reads a given maximum number of characters from the current stream.

ReadLine

Reads a line of characters from the current stream, returning them as a string.

ReadToEnd

Reads the stream from the current position to the end of the stream.

Here's an example, ch05_06.cs, which uses StreamReader to read a text file from disk and StreamWriter to write a copy of the file. This example opens its own source file, ch05_06.cs, and copies it to ch05_06.bak using text stream methods. In particular, this example uses the WriteLine and ReadLine methods to write and read whole lines of text at a time, as you'll see in ch05_06.cs, Listing 5.6. (Note that the ReadLine method returns null if there's no more data to read, letting us know when the program is finished reading the available data.)

Listing 5.6 Working with Text Files (ch05_06.cs)
 using System.IO; class ch05_06 {   public static void Main()   {  StreamReader input = new StreamReader("ch05_06.cs");   StreamWriter output = new StreamWriter("ch05_06.bak");   string inputString;   while ((inputString = input.ReadLine())!= null)   {   output.WriteLine(inputString);   }   input.Close();   output.Close();  } } 

When you run ch05_06.cs, it copies itself over into ch05_06.bak, line by line, using ReadLine and WriteLine , treating its data as text in a line-oriented way (as opposed to the FileStream class's Read and Write methods we saw earlier, which treat their data as binary). Note that an alternative to reading line by line in a loop is to use ReadToEnd , which reads all the text from your current position in the file, returning a single string.

Working with Asynchronous I/O

Up to this point, we've simply read data from a file and waited for the read operation to finish before doing anything. As we start working with network I/O, where things can be a lot slower, it won't be as easy to wait for reading and writing operations to complete. For that reason, the .NET Framework supports asynchronous I/O through the BeginRead and BeginWrite methods of the Stream class. You can call BeginRead to read a bufferfull of data, or BeginWrite to write a bufferfull of data, and then go on to do other work (we'll use a for loop for that purpose). You'll be called back when the read or write operation is complete.

Here's how it might work if you wanted to read a large file while doing other work at the same time. In this example, we'll create a method named StartReading to start the reading operation and then go on with other work. This method opens this example's source code file and calls the BeginRead method to read from the source code file into a buffer. To call BeginRead , you pass it the data buffer ( dataBuffer here), the offset in the buffer at which to start reading (0), the number of bytes to read ( dataBuffer.Length , using the Length array property), the delegate ( asyncDelegate ) to a callback method ( OnBufferFull ) which will be called when the buffer is full, and a state variable in which BeginRead can record the state of the current read operation ( null ). After the call to BeginRead , we'll turn to some other workexecuting a for loop one million times:

 
 FileStream input; byte[] dataBuffer; System.AsyncCallback asyncDelegate; void StartReading() {  input = new FileStream("ch05_07.cs",   FileMode.Open, FileAccess.Read, FileShare.None);   dataBuffer = new byte[512];   asyncDelegate = new System.AsyncCallback(this.OnBufferFull);   input.BeginRead(dataBuffer, 0, dataBuffer.Length, asyncDelegate, null);   for (int loopIndex = 0; loopIndex < 1000000; loopIndex++){}  } 

While the loop executes, BeginRead fills the buffer with data. After a bufferfull of data has been read, the method OnBufferFull is called with an IAsyncResult argument, temporarily interrupting the for loop. You can pass the IAsyncResult argument to the stream's EndRead or EndWrite methods to get the number of bytes actually read or written, and we'll display that number here. If the number of bytes read is greater than 0, we'll go back to read more data into the buffer with another call to BeginRead :

 
 void OnBufferFull(System.IAsyncResult asyncResult) {  int numberBytes = input.EndRead(asyncResult);   System.Console.WriteLine(numberBytes);   if (numberBytes > 0)   {   input.BeginRead(dataBuffer, 0, dataBuffer.Length, asyncDelegate, null);   }  } 

You can see the whole code in Listing 5.7, where we create an object of the ch05_07 class so we can call non-static methods from Main , and call the StartReading method to start the asynchronous read process. (Note that to justify asynchronous reading here, you should use this kind of code to read in a huge file instead of just the sample's own source code.)

Listing 5.7 Asynchronous File Reading (ch05_07.cs)
 using System.IO; public class ch05_07 {   FileStream input;   byte[] dataBuffer;   System.AsyncCallback asyncDelegate;   public static void Main()   {  ch05_07 appObj = new ch05_07();   appObj.StartReading();  }   void StartReading()   {     input = new FileStream("ch05_07.cs",       FileMode.Open, FileAccess.Read, FileShare.None);     dataBuffer = new byte[512];     asyncDelegate = new System.AsyncCallback(this.OnBufferFull);     input.BeginRead(dataBuffer, 0, dataBuffer.Length, asyncDelegate, null);     for (int loopIndex = 0; loopIndex < 1000000; loopIndex++){}   }   void OnBufferFull(System.IAsyncResult asyncResult)   {     int numberBytes = input.EndRead(asyncResult);     System.Console.WriteLine(numberBytes);     if (numberBytes > 0)     {       input.BeginRead(dataBuffer, 0, dataBuffer.Length, asyncDelegate, null);     }   } } 

The ch05_07.cs example is 1006 bytes long, and this is what you see when you run it. Note that it took two asynchronous buffered reads (512 + 494 = 1006) to read the file:

 
 C:\>ch05_07 512 494 0 

Working with Network I/O

In C#, you can read and write data using streams on the Internet much as we've already been doing with files. Network I/O is based on sockets , and a socket represents a connection to another socket somewhere on the network for the purposes of communication. You can use UDP or TCP/IP protocols with sockets, and we'll use the more common TCP/IP protocol here.

The type of applications we're going to build here are client/server applications. You build the client with the TcpClient class, and you build the server using the TcpListener class. Typically, the server listens for client connections, and when a client connects, a new Socket object is created. Using that socket, you can create a NetWorkStream object to send data to the client.

Creating a Network Server

Let's see how this works by creating a client/server TCP/IP application now. We'll start by building a server first, which will send its own source code to any client that connects to it, using an Internet socket. The server is based on the TcpListener class, and we'll use the Socket and NetworkStream classes to build it. You can see the significant public methods of the TcpListener class in Table 5.11, the significant public methods of the Socket class in Table 5.12, and the significant public methods of the NetworkStream class in Table 5.13.

Table 5.11. Significant Public TcpListener Methods

METHOD

PURPOSE

AcceptSocket

Accepts a connection request.

AcceptTcpClient

Accepts a TCP connection request.

Pending

Determines whether there are pending connection requests .

Start

Starts listening for connection requests.

Stop

Closes the listener.

Table 5.12. Significant Public Socket Methods

METHOD

PURPOSE

Accept

Creates a new socket for a new connection.

BeginAccept

Begins an asynchronous operation to accept an incoming connection.

BeginConnect

Begins an asynchronous request for a remote host connection.

BeginReceive

Begins to asynchronously receive data from a connected socket.

BeginReceiveFrom

Begins to asynchronously receive data from a given network device.

BeginSend

Sends data asynchronously to a connected socket.

BeginSendTo

Sends data asynchronously to a specific remote host.

Close

Closes the socket connection.

Connect

Establishes a connection to a remote host.

EndConnect

Ends a pending asynchronous connection request.

Listen

Places a socket in a listening state.

Receive

Receives data from a bound socket.

ReceiveFrom

Receives a datagram and stores the source endpoint.

Send

Sends data to a connected socket.

Shutdown

Disables, sends, and receives using a socket.

Table 5.13. Significant Public NetworkStream Methods

METHOD

PURPOSE

BeginRead

Begins an asynchronous read from the network stream.

BeginWrite

Begins an asynchronous write to a stream.

Close

Closes the network stream.

EndRead

Handles the end of an asynchronous read.

EndWrite

Handles the end of an asynchronous write.

Flush

Flushes data from the stream.

Read

Reads data from the network stream.

ReadByte

Reads a byte from the stream and advances the position within the stream by one byte.

Seek

Sets the current position of the stream to the given value.

SetLength

Sets the length of the stream.

Write

Writes data to the network stream.

WriteByte

Writes a byte to the current position in the stream and advances the position in the stream by one byte.

Servers listen for connections to a client, and you can pass the TcpListener class an IP address (an Internet address of the form xxx.xxx.xxx.xxx ; for example, microsoft.com 's IP address is 207.46.134.222 ) and a port to listen on (ports range from 0 to 65,535). In this example, we'll use the constant IPAddress.Any to indicate that we want to listen for connections on any network interface, and the port number 65512 . After you've created a TcpListener object in a method we'll call Listen , you start listening for connections from the client with the Start method:

 
 private void Listen() {  TcpListener tcpListener = new TcpListener(IPAddress.Any, 65512);   tcpListener.Start();  .     .     . 

We'll do the actual listening with an endless while loop, calling the TCP/IP listener's AcceptSocket method to create a new socket; if that socket's Connected property is true , we've connected to the client. In that case, we use the Socket object to create a new NetworkStream object, and use the NetworkStream object to create a new StreamWriter object so we can use the WriteLine method to write to the client. We'll also create a StreamReader object so we can read in this example's own source code. Here's how we read in that source code and send it to the client in the while loop:

 
 private void Listen() {   TcpListener tcpListener = new TcpListener(IPAddress.Any, 65512);   tcpListener.Start();   System.Console.WriteLine("Listening...");  while(true)   {   Socket socket = tcpListener.AcceptSocket();   if (socket.Connected)   {   System.Console.WriteLine("Connected...");   NetworkStream networkStream = new NetworkStream(socket);   StreamWriter output = new StreamWriter(networkStream);   StreamReader input = new StreamReader("ch05_08.cs");   string inputString;   System.Console.WriteLine("Sending...");   while((inputString = input.ReadLine()) != null)   {   output.WriteLine(inputString);   output.Flush();   }   .   .   .   }  System.Console.WriteLine("Quitting...");   } } 

IP ADDRESSES AND PORTS

To find an IP address, use the Windows ping utility like this: ping microsoft.com . Not all ports are available for programmer use; choosing a value above 65,000 is a good idea to avoid conflicts. To get a list of standard ports and their assigned uses, look at http://www.iana.org/assignments/port- numbers .


Note that after each WriteLine operation we call the Flush method to make sure all data was sent to the client and not cached. This is always a good idea with sockets.

All that's left is to call Listen from Main , close the streams when we're done, and break out of the while loop to finish the server's code. You can see how that works in the entire code in Listing 5.8.

Listing 5.8 A Network Server (ch05_08.cs)
 using System.IO; using System.Net; using System.Net.Sockets; public class ch05_08 {  public static void Main()   {   ch05_08 appObject = new ch05_08();   appObject.Listen();   }  private void Listen()   {     TcpListener tcpListener = new TcpListener(IPAddress.Any, 65512);     tcpListener.Start();     System.Console.WriteLine("Listening...");     while(true)     {        Socket socket = tcpListener.AcceptSocket();        if (socket.Connected)        {          System.Console.WriteLine("Connected...");          NetworkStream networkStream = new NetworkStream(socket);          StreamWriter output = new StreamWriter(networkStream);          StreamReader input = new StreamReader("ch05_08.cs");          string inputString;          System.Console.WriteLine("Sending...");          while((inputString = input.ReadLine()) != null)          {            output.WriteLine(inputString);            output.Flush();          }  networkStream.Close();   input.Close();   output.Close();   socket.Close();   System.Console.WriteLine("Disconnected...");   break;  }       System.Console.WriteLine("Quitting...");     }   } } 
Creating a Network Client

The next step is to create the client using the TcpClient class. You can see the significant public methods of the TcpClient class in Table 5.14.

Table 5.14. Significant Public TcpClient Methods

METHOD

PURPOSE

Close

Closes the TCP connection.

Connect

Connects the client to a remote TCP host using the given hostname and port number.

GetStream

Returns the network stream used to send and receive data.

We'll need a TcpClient object to create the client application for this example. You can pass the TcpClient constructor the name of a remote host and a port number to connect on, like this: ("www.microsoft.com", 80) , where port 80 is the one used for HTTP communication. In this example, we'll run both the client and the server on the same machine, using port 65512, so the name of the host will be "localHost" . To use the StreamReader ReadLine method to read text from the server, we'll use the TcpClient object's GetStream method to get its underlying NetWorkStream stream, and pass that stream to the StreamReader constructor like this:

 
 TcpClient client = new TcpClient("localHost", 65512); NetworkStream network = client.GetStream(); System.IO.StreamReader input = new System.IO.StreamReader(network); 

All that's left is to use ReadLine to keep reading text from the server, and then to close the connection, as you see in Listing 5.9.

Listing 5.9 A Network Client (ch05_09.cs)
 using System.Net.Sockets; public class ch05_09 {   static public void Main()   {     TcpClient client = new TcpClient("localHost", 65512);     NetworkStream network = client.GetStream();     System.IO.StreamReader input = new System.IO.StreamReader(network);  string outputString;   while((outputString = input.ReadLine()) != null)   {   System.Console.WriteLine(outputString);   }   input.Close();   network.Close();  } } 

That's all you need. Now start the server, ch05_08.exe, in one DOS window, and then the client, ch05_09.exe, in a second DOS window. The two will connect on port 65512; the server will send its own source code, ch05_08.cs, to the client, which will display it. Here's what you see when you run the server:

 
 C:\>ch05_08 Listening... Connected... Sending... Disconnected... 

Here's what you see when you run the client. As you can see, we've connected using Internet sockets and ports:

 
 C:\>ch05_09 using System.IO; using System.Net; using System.Net.Sockets; public class ch05_08 {     .     .     . 

Supporting Asynchronous Network I/O

The previous example used synchronous network I/O, but if you have a number of clients all trying to connect to your server at once, it's a better idea to use asynchronous I/O. To handle multiple clients , for example, you might create a new class, Client , and create a new object of that class to handle each new client as requests come in. To perform the actual asynchronous reads and writes in that object, you can use BeginRead and BeginWrite , as we did earlier in this chapter.

You can see an example of this in ch05_10.cs, which is an asynchronous network I/O server, and ch05_11.cs, which is an asynchronous client. In this case, the server will be set up for both asynchronous reads and writes. The client will send it some text (in this example, that's the text message "Network Streaming!" ), and the server will read that text asynchronously and write it back asynchronously, giving the server the time it needs to handle other clients.

In order to handle multiple clients, you can pass the connected socket to the Client class's constructor when a new client connects, thus creating a new Client object that will handle the new client. This enables you to accept as many incoming connections as you want to:

 
 public static void Main() {   TcpListener tcpListener = new TcpListener(IPAddress.Any, 65512);   tcpListener.Start();   System.Console.WriteLine("Waiting for connection...");  while (true)   {   Socket socket = tcpListener.AcceptSocket();   if (socket.Connected)   {   Client client = new Client(socket);   }   }  } 

CONVERTING BYTE ARRAYS INTO STRINGS

Note the handy utility method we call to convert the text in the data buffer into a string object in Listing 5.10 System.Text.Encoding.ASCII.GetString . You can pass char arrays to the String class's constructor, but not byte arrays of the kind we must use here (and C# won't let you cast from a byte array to a char array), so System.Text.Encoding.ASCII.GetString is a good method to use.


You can see how the Client class works in Listing 5.10; when you create an object of this class, its constructor starts reading from the client with BeginRead . When text has been read, BeginWrite is called to write the received text back to the client, and the code tries to read more from the client. If there's no more to read, the code closes the connection.

Listing 5.10 An Asynchronous Network Server (ch05_10.cs)
 using System.Net; using System.Net.Sockets; public class ch05_10 {   public static void Main()   {     TcpListener tcpListener = new TcpListener(IPAddress.Any, 65512);     tcpListener.Start();     System.Console.WriteLine("Waiting for connection...");     while (true)     {       Socket socket = tcpListener.AcceptSocket();       if (socket.Connected)       {         Client client = new Client(socket);       }     }   } }  class Client   {   byte[] dataBuffer;   Socket socket;   NetworkStream networkStream;   System.AsyncCallback readDelegate;   System.AsyncCallback writeDelegate;   public Client(Socket socket)   {   this.socket = socket;   dataBuffer = new byte[512];   networkStream = new NetworkStream(socket);   System.Console.WriteLine("Connected to a client...");   readDelegate = new System.AsyncCallback(this.OnRead);   writeDelegate = new System.AsyncCallback(this.OnWrite);   networkStream.BeginRead(dataBuffer, 0,   dataBuffer.Length, readDelegate, null);   }   private void OnRead(System.IAsyncResult asyncResult)   {   int numberBytes = networkStream.EndRead(asyncResult);   if (numberBytes > 0)   {   string outputString = System.Text.Encoding.ASCII.GetString(   dataBuffer, 0, numberBytes - 2);   System.Console.WriteLine("Read this text: \"{0}\".", outputString);   networkStream.BeginWrite(dataBuffer, 0, numberBytes,   writeDelegate, null);   }   else   {   System.Console.WriteLine(   "Read operation with this client finished...");   networkStream.Close();   networkStream = null;   socket.Close();   socket = null;   }   }   private void OnWrite(System.IAsyncResult asyncResult)   {   networkStream.EndWrite(asyncResult);   System.Console.WriteLine("Sent text back to client...");   networkStream.BeginRead(dataBuffer, 0, dataBuffer.Length,   readDelegate, null);   }   }  

The client in this case needs to connect to the server with a NetworkStream object, create a StreamWriter object to write to the server, and create a StreamReader object to read from the server. You can see how that works in the client's code, ch05_11.cs, Listing 5.11.

Listing 5.11 An Asynchronous Network Client (ch05_11.cs)
 using System.Net.Sockets; public class ch05_11 {   static public void Main()   {    ch05_11 appObject = new ch05_11();   }   ch05_11()   {  NetworkStream networkStream;   System.Console.WriteLine("Connecting to server...");   TcpClient tcpSocket = new TcpClient("localHost", 65512);   networkStream = tcpSocket.GetStream();   string outputString = "Network streaming!";   System.Console.WriteLine("Sending this message to server: \"{0}\".",   outputString);   System.IO.StreamWriter output = new System.IO.StreamWriter(networkStream);   output.WriteLine(outputString);   output.Flush();   System.IO.StreamReader input = new System.IO.StreamReader(networkStream);   string inputString = input.ReadLine();   System.Console.WriteLine("Got this message from server: \"{0}\".",   inputString);   System.Console.WriteLine("Disconnecting from server...");   networkStream.Close();   }   }  

When you start the server, ch05_10.exe, you'll see this:

 
 C:\>ch05_10 Waiting for connection... 

Starting a client, ch05_11.exe, in a new DOS window shows how the client can send the message, "Network Streaming!" to the server, and how the server sends it back:

 
 C:\c#\ch05>ch05_11 Connecting to server... Sending this message to server: "Network streaming!". Got this message from server: "Network streaming!". Disconnecting from server... 

Here's what the server displays after the connection is made:

 
 C:\>ch05_10 Waiting for connection...  Connected to a client...   Read this text: "Network streaming!".   Sent text back to client...   Read operation with this client finished...  

You can make multiple connections to this server, running the client application in multiple DOS windows. Each time you do, a new Client object will be created to handle the new connection, and read/write operations with the new client will be handled asynchronously. That means that one client doesn't have to wait until the server is done with all other clients before it gets any attention, which is how it's done in the real world.

Working with Internet Streams

The FCL also includes the HttpWebRequest and HttpWebResponse classes to let you work directly with HTTP streams on the Web. We'll take a look at how to send a request to a Web server, requesting the Web page at www.microsoft.com, and reading the Web server's response when it comes in. You can see the significant public properties of the HttpWebRequest class, which enable you to send commands to Web servers just as browsers do, in Table 5.15, and its significant public methods in Table 5.16.

Table 5.15. Significant Public HttpWebRequest Properties

PROPERTY

PURPOSE

Accept

Returns or sets the value of the Accept HTTP header.

Address

Returns the URI of the Internet resource that responds to the request.

Connection

Returns or sets the value of the Connection HTTP header.

ContentLength

Returns or sets the Content-length HTTP header.

ContentType

Returns or sets the value of the Content-type HTTP header.

CookieContainer

Returns or sets the cookies associated with the request.

Headers

Returns a collection of the name/value pairs that make up the HTTP headers.

KeepAlive

Returns or sets whether to connect to the Internet resource in a persistent way.

MediaType

Returns or sets the media type of the request.

Method

Returns or sets the method for the request.

Timeout

Returns or sets the time-out value for a request.

UserAgent

Returns or sets the value of the User-agent HTTP header.

Table 5.16. Significant Public HttpWebRequest Methods

METHOD

PURPOSE

Accept

Returns or sets the value of the Accept HTTP header.

Abort

Cancels a request.

BeginGetResponse

Begins an asynchronous request to an Internet resource.

EndGetResponse

Ends an asynchronous request to an Internet resource.

GetRequestStream

Returns a stream object to use to write request data.

GetResponse

Returns a response from an Internet resource.

The HttpWebResponse class lets you handle what the Web server sends back to you. You'll find the significant public properties of the HttpWebResponse class in Table 5.17, and its significant public methods in Table 5.18.

Table 5.17. Significant Public HttpWebResponse Properties

PROPERTY

PURPOSE

Accept

Returns or sets the value of the Accept HTTP header.

CharacterSet

Returns the character set of the response.

ContentLength

Returns the length of the content.

ContentType

Returns the content type of the response.

Cookies

Returns or sets cookies.

Headers

Returns the headers for this response from the server.

LastModified

Returns the last date and time that the response contents were modified.

Method

Returns the method used to return the response.

ProtocolVersion

Returns the version of the HTTP protocol used for the response.

ResponseUri

Returns the URI of the Internet resource that responded to the request.

Server

Returns the name of the server that sent the response.

StatusCode

Returns the status of the response.

StatusDescription

Returns the status description of the response.

Table 5.18. Significant Public HttpWebResponse Methods

METHOD

PURPOSE

Close

Closes the response stream.

GetResponseHeader

Returns the contents of a header returned with the response.

GetResponseStream

Returns a stream object used to read the body of the response.

Browsers work by sending the kinds of requests to Web servers that we'll send here, and by receiving the kinds of responses we'll get in return. In this next example, the application will act much like a browser when we use HttpWebRequest to send a request for the HTML of the page www.microsoft.com , and HttpWebResponse to create a StreamReader object so we can read that page. To create the request for that page, you don't use the HttpWebRequest constructor directly; you use the Create method of its base class, WebRequest . To get the response from the Web site, you use the GetResponse method of the WebRequest class. And to get a StreamReader stream corresponding to the Web page itself, you use the WebResponse class's GetResponseStream method, which returns a Stream object that you can pass on to the StreamReader constructor:

 
 HttpWebRequest webRequest = (HttpWebRequest)   WebRequest.Create("http://www.microsoft.com"); HttpWebResponse webResponse = (HttpWebResponse) webRequest.GetResponse(); StreamReader input = new StreamReader(webResponse.GetResponseStream()); 

Now that we have a StreamReader object, all we have to do is to read the Web page using the ReadLine method, as you see in ch05_12.cs, Listing 5.12.

Listing 5.12 An Asynchronous Network Client (ch05_12.cs)
 using System.IO; using System.Net; public class ch05_12 {   static public void Main( string[] Args )   {     HttpWebRequest webRequest = (HttpWebRequest)       WebRequest.Create("http://www.microsoft.com");     HttpWebResponse webResponse = (HttpWebResponse) webRequest.GetResponse();     StreamReader input = new StreamReader(webResponse.GetResponseStream());  string inputString;   while ((inputString = input.ReadLine()) != null)   {   System.Console.WriteLine(inputString);   }   input.Close();  } } 

When you run ch05_12, it downloads the HTML of the Web page at www.microsoft.com and displays it. That's all there is to it.



Microsoft Visual C#. NET 2003 Kick Start
Microsoft Visual C#.NET 2003 Kick Start
ISBN: 0672325470
EAN: 2147483647
Year: 2002
Pages: 181

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net