< BinaryWriter & BinaryReader Examples | Main | NetworkStream Receiver Examples >

 


 

Chapter 2 Part 5:

Managed I/O - Streams, Readers, and Writers

 

 

What do we have in this chapter 2 Part 5?

  1. Memory Stream

  2. Creating a Memory Stream

  3. C++ Program Example: MemoryStream

  4. C# Program Example: MemoryStream

  5. Handling Memory Stream I/O

  6. Network Stream

  7. Creating a Network Stream

  8. Handling Network I/O

 

 

 

Note: If you want to experience a complete C++ .NET/C++-CLI programming tutorial please jump to Visual C++ .NET programming tutorial.

 

Memory Stream

 

Memory streams, which perform I/O on memory resources, are another type of base stream. Memory streams are very similar to file streams, except that data is read and written to a memory buffer rather than to a file on a disk. You might even be wondering why there’s a memory stream class in the first place, especially when you can allocate memory from an application and write directly to a data buffer. From the standpoint of reading and writing data to memory, a memory stream isn’t much different from a buffer. One benefit in having memory accessible in the form of a stream is that streams are composable, meaning that you can build one on top of the other very easily, as mentioned earlier. For example, you can have a memory stream that’s layered below a composable encryption stream that’s layered below a composable compression stream, so at the end of the day, your application is writing compressed encrypted data to memory using one consistent I/O pattern.

 

Creating a Memory Stream

 

The MemoryStream class allows you to create a memory stream with a memory buffer as a backing store of a fixed size or of a dynamic size. The MemoryStream constructor features several prototypes that allow you to pass in a fixed byte-type array buffer that the stream internally uses as a backing store to read and write data. Otherwise, you can specify a size in bytes where the stream will dynamically allocate and manage a buffer for you.

 

C++ Example: MemoryStream

 

Create a new CLR console application project and you might want to use MemoryStreamSampleCP as the project and solution names.

 

C++/CLI Example: MemoryStream - CLR console application project example

 

Add the following code.

 

 

// MemoryStreamSampleCP.cpp : main project file.

 

#include "stdafx.h"

 

using namespace System;

using namespace System::IO;

 

[STAThread]

int main(array<System::String ^> ^args)

{

    // To work with Memory requires managing byte allocations in chunks

    // or blocks. The MemoryStream class allow you to create a memory stream

    // of a fixed size or a dynamic size. For this sample we will choose

    // the dynamic memory allocation mechanism by passing no parameters to the Memory stream constructor.

    MemoryStream^ MyMemoryStream = nullptr;

 

    try

    {

        try

        {

            MyMemoryStream = gcnew MemoryStream();

            Console::WriteLine("Instantiating MemoryStream()...");

        }

        catch (Exception^ e)

        {

            throw gcnew Exception("Failed to create a memory stream with error: " + e->Message);

        }

 

        // For curiosity sake, let's see how much memory our stream has allocated before we start writing bytes.

        Console::WriteLine("The memory stream has allocated " + MyMemoryStream->Capacity.ToString() + " bytes.");

        // Let's write 9 X 4000 bytes to Memory using the stream. For simplicity

        // we will write an array of 4000 bytes 9 times where each byte contains a numeric value of 1.

        Console::WriteLine("Writing 9 X 4000 bytes to memory...");

        array< Byte >^ MyByteArray = gcnew array< Byte >(4000);

 

         for (int i = 0; i < MyByteArray->Length; i++)

        {

            MyByteArray[i] = 1;

        }

 

        int TotalBytesWritten = 0;

 

        Console::WriteLine("Writing using Write()...");

        for (int j = 0; j < 9; j++)

        {

            try

            {

                MyMemoryStream->Write(MyByteArray, 0, MyByteArray->Length);

            }

            catch (Exception^ e)

            {

                throw gcnew Exception("Write failed with error: " + e->Message);

            }

            TotalBytesWritten = TotalBytesWritten + MyByteArray->Length;

        }

 

         Console::WriteLine("We wrote " + TotalBytesWritten.ToString() + " bytes to the memory stream.");

        // For curiosity sake once again, let's see how much memory our stream has

        // allocated after we have written the bytes.

        Console::WriteLine("The memory stream has allocated " + MyMemoryStream->Capacity.ToString() + " bytes.");

        // Now let's prepare to read the bytes that we have just written. To

        // do so requires seeking back to the beginning of the Memory buffer and reading the bytes.

        Console::WriteLine();

        try

        {

             MyMemoryStream->Seek(0, SeekOrigin::Begin);

             Console::WriteLine("Seek() is OK...");

        }

        catch (Exception^ e)

        {

            throw gcnew Exception("Seek() failed with error: " + e->Message);

        }

        // Now we may begin reading the bytes that were originally written. Since

        // our data is sort of boring, we will count the number of bytes read and print the total to the screen.

        array< Byte >^ MyReadBuffer = gcnew array< Byte >(1);

 

        int ByteTotal = 0;

       

        Console::WriteLine("Reading using Read()...");

        while (true)

        {

            int BytesRead;

 

            try

            {

                BytesRead = MyMemoryStream->Read(MyReadBuffer, 0, MyReadBuffer->Length);

            }

            catch (Exception^ e)

            {

                throw gcnew Exception("Read() failed with error: " + e->Message);

            }

 

            if (BytesRead == 0)

            {

                break;

            }

            ByteTotal = ByteTotal + BytesRead;

        }

        Console::WriteLine("We read " + ByteTotal.ToString() + " bytes from the memory stream.");

    }

    catch (Exception^ e)

    {

         Console::WriteLine(e->Message);

    }

    finally

    {

        // We are finished performing IO on the stream. We need to close the stream

        // to release related operating system resources.

        Console::WriteLine("Closing the MemoryStream...");

        MyMemoryStream->Close();

    }

    return 0;

}

 

Build and run the project. The following is the output example when run at the Windows console/command prompt.

 

C++ Example: MemoryStream - CLR console application project example - output sample

 

C# Example: MemoryStream

 

Create a new console application project. You can use the solution and project name as shown in the following Figure.

 

C# Example: MemoryStream - console application project example

 

Add the following code.

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.IO;

 

// <summary>

// This is a very simple sample demonstrating how to create a memory

// stream, write bytes to the memory buffer, and read bytes from the buffer.

// </summary>

namespace MemoryStreamSampleCS

{

    class Program

    {

        // <summary>

        // The main entry point for the application.

        // </summary>

        [STAThread]

        static void Main(string[ ] args)

        {

            // To work with Memory requires managing byte allocations in chunks

            // or blocks. The MemoryStream class allow you to create a memory stream

            // of a fixed size or a dynamic size. For this sample we will choose

            // the dynamic memory allocation mechanism by passing no parameters to the Memory stream constructor.

            MemoryStream MyMemoryStream = null;

 

            try

            {

                try

                {

                    MyMemoryStream = new MemoryStream();

                    Console.WriteLine("Instantiating new MemoryStream()...");

                }

                catch (Exception e)

                {

                    throw new Exception("Failed to create a memory stream with error: " + e.Message);

                }

 

                // For curiosity sake, let's see how much memory our stream has allocated before we start writing bytes.

                Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes.");

                // Let's write 9 X 4000 bytes to Memory using the stream. For simplicity

                // we will write an array of 4000 bytes 9 times where each byte contains a numeric value of 1.

                Console.WriteLine("Writing 9 X 4000 bytes to memory...");

                byte[ ] MyByteArray = new byte[4000];

 

                for (int i = 0; i < MyByteArray.Length; i++)

                {

                    MyByteArray[i] = 1;

                }

 

                int TotalBytesWritten = 0;

 

                Console.WriteLine("Writing using Write()...");

                for (int j = 0; j < 9; j++)

                {

                    try

                    {

                        MyMemoryStream.Write(MyByteArray, 0, MyByteArray.Length);

                    }

                    catch (Exception e)

                    {

                        throw new Exception("Write failed with error: " + e.Message);

                    }

                    TotalBytesWritten = TotalBytesWritten + MyByteArray.Length;

                }

 

                Console.WriteLine("We wrote " + TotalBytesWritten.ToString() + " bytes to the memory stream.");

                // For curiosity sake once again, let's see how much memory our stream has

                // allocated after we have written the bytes.

                Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes.");

                // Now let's prepare to read the bytes that we have just written. To

                // do so requires seeking back to the beginning of the Memory buffer and reading the bytes.

                Console.WriteLine();

                try

                {

                    MyMemoryStream.Seek(0, SeekOrigin.Begin);

                    Console.WriteLine("Seek() is OK");

                }

                catch (Exception e)

                {

                    throw new Exception("Seek() failed with error: " + e.Message);

                }

 

                // Now we may begin reading the bytes that were originally written. Since

                // our data is sort of boring, we will count the number of bytes read and print the total to the screen.

                byte[ ] MyReadBuffer = new byte[1];

 

                int ByteTotal = 0;

               

                Console.WriteLine("Reading using Read()...");

                while (true)

                {

                    int BytesRead;

 

                    try

                    {

                        BytesRead = MyMemoryStream.Read(MyReadBuffer, 0, MyReadBuffer.Length);

                    }

                    catch (Exception e)

                    {

                        throw new Exception("Read() failed with error: " + e.Message);

                    }

 

                    if (BytesRead == 0)

                    {

                        break;

                    }

                    ByteTotal = ByteTotal + BytesRead;

                }

                Console.WriteLine("We read " + ByteTotal.ToString() + " bytes from the memory stream.");

            }

            catch (Exception e)

            {

                Console.WriteLine(e.Message);

            }

            finally

            {

                // We are finished performing IO on the stream. We need to close the stream

                // to release related operating system resources.

                Console.WriteLine("Closing the MemoryStream...");

                MyMemoryStream.Close();

            }

        }

    }

}

 

An output sample:

 

  C# Example: MemoryStream - console application project example - output sample

 

VB .NET Example: MemoryStream

 

Create a new console application project. You can use the solution and project name as shown in the following Figure.

 

VB .NET Example: MemoryStream - console application project example

 

Add the following code.

 

Imports System

Imports System.IO

 

'This is a very simple sample demonstrating how to create a memory

'stream, write bytes to the memory buffer, and read bytes from the buffer.

Module Module1

    'The main entry point for the application.

    Sub Main()

 

        ' To work with Memory requires managing byte allocations in chunks

        ' or blocks. The MemoryStream class allow you to create a memory stream

        ' of a fixed size or a dynamic size. For this sample we will choose

        ' the dynamic memory allocation mechanism by passing no parameters to the Memory stream constructor.

        Dim MyMemoryStream As System.IO.MemoryStream = Nothing

 

        Try

            Try

                Console.WriteLine("Instantiating MemoryStream object...")

                MyMemoryStream = New System.IO.MemoryStream

            Catch e As Exception

                Throw New Exception("Failed to create a memory stream with error: " + e.Message)

            End Try

 

            ' For curiosity sake, let's see how much memory our stream has allocated before we start writing bytes.

            Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes.")

            ' Let's write 9 X 4000 bytes to Memory using the stream. For simplicity

            ' we will write an array of 4000 bytes 9 times where each byte contains a numeric value of 1.

            Console.WriteLine("Writing 9 x 4000 bytes to memory...")

            Dim MyByteArray(4000) As Byte

            Dim i As Integer

 

            For i = MyByteArray.GetLowerBound(0) To MyByteArray.GetUpperBound(0) - 1

                MyByteArray(i) = 1

            Next

            Dim TotalBytesWritten, j As Integer

            TotalBytesWritten = 0

            Console.WriteLine("Writing using Write()...")

            For j = 0 To 8

                Try

                    MyMemoryStream.Write(MyByteArray, 0, MyByteArray.GetUpperBound(0))

                Catch e As Exception

                    Throw New Exception("Write() failed with error: " + e.Message)

                End Try

                TotalBytesWritten = TotalBytesWritten + MyByteArray.GetUpperBound(0)

            Next

            Console.WriteLine("We wrote " + TotalBytesWritten.ToString() + " bytes to the memory stream.")

            ' For curiosity sake once again, let's see how much memory our stream has

            ' allocated after we have written the bytes.

            Console.WriteLine("The memory stream has allocated " + MyMemoryStream.Capacity.ToString() + " bytes.")

            ' Now let's prepare to read the bytes that we have just written. To

            ' do so requires seeking back to the beginning of the Memory buffer and reading the bytes.

            Console.WriteLine()

            Try

                MyMemoryStream.Seek(0, SeekOrigin.Begin)

                Console.WriteLine("Seek() is OK...")

            Catch e As Exception

                Throw New Exception("Seek() failed with error: " + e.Message)

            End Try

            ' Now we may begin reading the bytes that were originally written. Since

            ' our data is sort of boring, we will count the number of bytes read and print the total to the screen.

            Dim MyReadBuffer(1) As Byte

            Dim ByteTotal As Integer = 0

 

            Console.WriteLine("Reading using Read()...")

            While True

                Dim BytesRead As Integer

                Try

                    BytesRead = MyMemoryStream.Read(MyReadBuffer, 0, MyReadBuffer.GetUpperBound(0))

                Catch e As Exception

                    Throw New Exception("Read() failed with error: " + e.Message)

                End Try

                If (BytesRead = 0) Then

                    Exit While

                End If

                ByteTotal = ByteTotal + BytesRead

            End While

            Console.WriteLine("We read " + ByteTotal.ToString() + " bytes from the memory stream.")

        Catch e As Exception

            Console.WriteLine(e.Message)

        Finally

            ' We are finished performing IO on the stream. We need to close the stream

            ' to release related operating system resources.

            Console.WriteLine("Closing the MemoryStream...")

            MyMemoryStream.Close()

        End Try

    End Sub

End Module

 

An output sample:

 

VB .NET Example: MemoryStream - console application project example - sample output

 

The following code fragment creates a memory stream that does not specify a memory size. In this case, the stream will automatically allocate memory as needed as data bytes are written to the stream. Initially, the backing store is set to zero bytes.

 

C#

 

// Code fragment using C#

MemoryStream MyMemoryStream = null;

try

{

    MyMemoryStream = new MemoryStream();

    Console.WriteLine("Instantiating new MemoryStream()...");

}

catch (Exception e)

{

    throw new Exception("Failed to create a memory stream with error: " + e.Message);

    return;

}

finally

{

   // We are finished performing IO on the stream. We need to close the stream

   // to release related operating system resources.

   Console.WriteLine("Closing the MemoryStream...");

   MyMemoryStream.Close();

}

 

Visual Basic .NET

 

// Code fragment using VB       

Dim MyMemoryStream As System.IO.MemoryStream = Nothing

 

    Try

        Try

            Console.WriteLine("Instantiating MemoryStream object...")

            MyMemoryStream = New System.IO.MemoryStream

        Catch e As Exception

            Throw New Exception("Failed to create a memory stream with error: " + e.Message)

      End Try

 

Handling Memory Stream I/O

 

Reading and writing to a MemoryStream is just like handling I/O in a FileStream, as described earlier, except that if you create a memory stream with a fixed buffer, you might experience an IOException if you attempt to write more bytes than the buffer can handle. Another important item worth mentioning is that it’s important to call Close() on a dynamically allocated memory stream. Close() effectively releases the memory associated with the stream, and the data written there is no longer available.

 

Network Stream

 

Network streams allow you to communicate between processes over a network or even on the same computer. Network streams rely on the Sockets class from the System.Net namespace as a backing store to communicate from one socket to another, and any application can create one or more sockets to communicate. Network streams require stream-oriented sockets to form a backing store and work only with connection-oriented network protocols such as TCP/IP. Network streams do not work with datagram-based network protocols such as User Datagram Protocol (UDP). Stream-oriented sockets form a virtual connection between two socket pairs to transmit and receive data in sequential order. One of the most popular stream-oriented sockets is one that communicates over the TCP/IP protocol. There are many other ways that sockets can be created and controlled, which is the topic of discussion in Chapter 12. Utilizing network streams requires a complete understanding of how to set up a stream- oriented connected socket pair. For our discussion of network streams, we’ll present only TCP/IP sockets. In this chapter, we’ll gloss over the details of creating and setting up a stream-oriented socket connection, and the code fragment in this section will assume that a valid socket connection exists.

Compared to file and memory streams, sockets behave quite differently as a backing store. Stream-oriented connected sockets are bidirectional, which means that there are two communication paths within the connected socket pairs. If you write data to Socket A, you will be able to read the same data only on a peer-Socket B. Figure 2-3 shows bidirectional flow by writing the letters of the alphabet in order from Socket A and receiving the letters on Socket B. In the other direction, we write the numbers 1 through 3 from Socket B and they are read from Socket A. You will not be able to read the data you originally wrote to Socket A from the same socket. The same behavior is true if you write data to Socket B where you’ll be required to read the data on Socket A.

 

.NET bidirectional stream-oriented connected sockets

 

Figure 2-3: Bidirectional stream-oriented connected sockets

 

Another major difference in a network stream as compared to other streams is that a network stream doesn’t maintain the stream Position property, which means that you can’t call Seek() or change the Position of the stream. When a network stream reads data from a Socket, the data becomes consumed and is no longer available to be read again. Therefore, the network stream is unable to maintain the Position pointer. For example, in Figure 2-3 on step 2, Socket B can read the A B C characters only one time. Once the characters are read, the characters get removed from the stream. Therefore, if you try to change the Position property or call Seek(), you’ll get a NotSupportedException.

 

Creating a Network Stream

 

Creating a network stream requires having a socket connection established with another socket before you create the network stream to perform I/O. If your socket is not connected, the NetworkStream constructor will raise an ArgumentException. Also, your socket must be running in blocking mode, which is the default behavior when you create a socket. If your socket is non-blocking, an IOException will be raised. For more information about blocking and non- blocking sockets, see Chapter 8. The following code fragment demonstrates how to create a network stream on top of a socket named MySocket:

 

C#

 

Socket  MySocket;

// Assume we have a connected socket already created.

NetworkStream MyNetworkStream;

 

try

{

    // Setup a network stream on a connected Socket

    MyNetworkStream = new NetworkStream(MySocket, true);

}

catch (Exception e)

{

    Console.WriteLine("Failed to create network stream with error: " + e.Message);

}

finally

{

    MyNetworkStream.Close();

}

 

Visual Basic .NET

 

Dim MySocket As Socket

 

' Assume we have a connected Socket already created.

Dim MyNetworkStream As System.Net.Sockets.NetworkStream

 

Try

' Setup a network stream on the client Socket

    MyNetworkStream = New System.Net.Sockets.NetworkStream(MySocket, True)

Catch e As Exception

    Console.WriteLine("Failed to create a network stream " & "with error: " + e.Message)

Finally

    MyNetworkStream.Close()

End Try

 

The NetworkStream constructor features a useful parameter named ownsSocket that allows the network stream to own the underlying socket. When this parameter is true, NetworkStream’s Close() method will release the resources of the network stream and close the underlying socket in one call. When the ownsSocket parameter is false, the underlying socket will stay open for more communication. Your application will have to close the socket at a later point.

 

Handling Network I/O

 

As we mentioned earlier, reading and writing to a network stream is a lot different from performing I/O with file and memory streams because network streams use sockets as a backing store. Sockets are bidirectional, as you write data on a socket, you can’t receive the same data on the same socket. Also, as you read a socket, the I/O operation can block until the peer socket writes data on your socket or until the peer closes the network stream. Additionally, write operations can block if the peer socket does not read data. When reading data from a network stream, you have to check how many bytes are read into your data buffer supplied to the Read() operation. If the number of bytes is zero, the peer socket has closed down the communication stream. The following code fragment demonstrates how to read bytes from a network stream named MyNetworkStream that we created earlier:

 

C#

 

try

{

int BytesRead = 0;

byte [ ] ReadBuffer = new byte[4096];

 

    do

    {

        BytesRead = MyNetworkStream.Read(ReadBuffer, 0, ReadBuffer.Length);

        Console.WriteLine("We read " + BytesRead.ToString() + " bytes from a peer socket.");

    }

    while (BytesRead > 0);

}

catch (Exception e)

{

    Console.WriteLine("Reading a network stream failed with error: " + e.Message);

}

 

Visual Basic .NET

 

Try

Dim BytesRead As Integer

    BytesRead = 0

 

Dim ReadBuffer(4096) As Byte

 

    Do

        BytesRead = MyNetworkStream.Read(ReadBuffer, 0, ReadBuffer.GetUpperBound(0))

            Console.WriteLine("We read " + BytesRead.ToString() + " bytes from a peer socket.")

    Loop While BytesRead > 0

Catch e As Exception

    Console.WriteLine("Reading a network stream failed " & "with error: " + e.Message)

End Try

 

Writing to a network stream is very straightforward. The only thing you have to worry about is a peer closing a connection because the Write() operation will raise an IOException indicating that the peer is no longer available to receive the data that was written. The following code fragment shows how to write to the MyNetworkStream network stream described earlier:

 

C#

 

// Assume we have a buffer with 1024 bytes

try

{

    MyNetworkStream.Write(Buffer, 0, Buffer.Length);

}

catch (Exception e)

{

    Console.WriteLine("Failed to write to network stream with error: " + e.Message);

}

 

Visual Basic .NET

 

Try

    MyNetworkStream.Write(Buffer, 0, Buffer.GetUpperBound(0))

 

Catch e As Exception

    Console.WriteLine("Failed to write to network stream with error: " + e.Message)

End Try

 

Once all I/O has completed on a network stream, it’s important to call the Close() method to free any resources associated with the network stream. Also, if the network stream owns the underlying socket, calling Close() will free up socket network resources associated with the network stream.

 

The following examples contains two network stream samples called Sender and Receiver that enable you to send data bytes from the Sender to the Receiver application across a network or even on the same machine. The Receiver application is designed to listen for a network stream connection and receive data as it arrives. The Sender application is designed to connect to the Receiver application’s network stream and send data bytes to the Receiver.

 

 

 


 

< BinaryWriter & BinaryReader Examples | Main | NetworkStream Receiver Examples >