Over the course of the following weeks, I am going to be publishing a series of blog posts on C#. I’m of course not talking about the musical note but rather the programming language. The purpose of these blog posts is to introduce you to some of the key concepts in C#. I would be stupid to say you’ll become an expert from this because there’s alot of different aspects to C# and it can’t all be explained in just four blog posts. In this first part, I will examine the different types of streams and how you can use them.
Basically, a stream is a sequence of bytes that contains any type of information that can be located anywhere on a computer or network. System.IO.Stream is a abstract class so it must be inherited by another class in order for it to be used. Some streams can only be written, some can only be read, and others can be both read and written to.
Stream Sources
The follow explains the various classes (built-in to C#) that inherit and implement the System.IO.Stream class. One major limitation with these streams is that they can only read and write in bytes, as oppose to doing something like reading or writing as a string. To make it simpler, look at the section below on the helper classes that will make it simpler to work with streams.
System.IO.FileStream
The FileStream allows you to read and/or write to a file on the hard drive. For our purposes, we’re going to look at opening a FileStream using the file path, file mode, and file access. A FileStream can be created using the path to a file (as a string) or a file handle. A file handle is usually created using a Platform Invoke (or P/Invoke) to call the CreateFile function, which is bit advanced and I’m not going to get into the details.
This example creates a new file (or erases everything inside if it already exists) at “C:\test.txt” with the ability to write to it:
[highlight lang=”cs”]FileStream stream;
string filePath = “C:\\test.txt”;
FileMode fileMode = FileMode.CreateNew;
FileAccess fileAccess = FileAccess.Write;
try
{
stream = new FileStream(filePath, fileMode, fileAccess);
}
catch (Exception)
{
// Unable to open FileStream
}[/highlight]
More information about the FileStream class can be found on the MSDN website.
System.IO.MemoryStream
With a MemoryStream, you can have the data stored in memory. Unlike a FileStream where it’s saved to the hard drive, a MemoryStream is only saved while the program is running. This is good for when you want the data to be temporary. A MemoryStream can be created from an existing byte array and be a fixed or expandable size.
This example creates a MemoryStream with a capacity (or maximum size) of 5120 bytes (equal to 5 kilobytes):
[highlight lang=”cs”]MemoryStream stream;
int capacity = 5120;
try
{
stream = new MemoryStream(capacity);
}
catch (Exception)
{
// Unable to open MemoryStream
}[/highlight]
More information about the MemoryStream class can be found on the MSDN website.
System.IO.BufferedStream
To explain the BufferedStream, first I should explain what a buffer is. In programming, a buffer is a block of memory that is used as a cache for input/output operations. When data is read from a file, ordinarily it will read one byte at a time and store it in memory. Especially when dealing with large files, reading one byte at a time will become slow and put a strain on the hard drive. A BufferedStream fixes this by reading/writing a block of data at time and storing it in memory (where it can be accessed quicker).
The BufferedStream is probably the least used stream because Microsoft has it integrated with all the other stream types. You can use BufferedReader with something that already implements it (like FileStream) and this will create what’s called a double buffer (which improves reliability). This is a helper class (the constructor takes an existing Stream as a parameter) and it is sealed, so it cannot be inherited by another class.
Since BufferedStream is a helper class, this example creates a double buffered MemoryStream:
[highlight lang=”cs”]BufferedStream stream;
try
{
stream = new BufferedStream(new MemoryStream());
}
catch (Exception)
{
// Unable to open BufferedStream
}[/highlight]
More information about the BufferedStream class can be found on the MSDN website.
System.Net.Sockets.NetworkStream
Anytime you have a Socket and want to communicate with the remote host, you would use the NetworkStream. This stream is a helper class and can only be used with a Socket instance. This class is located in the System.Net.Sockets namespace and not the System.IO namespace.
The following example connects to google.com on port 80 and creates a NetworkStream to send and receive data:
[highlight lang=”cs”]Socket socket;
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(“www.google.com”, 80);
}
catch (Exception)
{
// Unable to open connection
return;
}
NetworkStream stream;
try
{
stream = new NetworkStream(socket);
}
catch (Exception)
{
// Unable to open NetworkStream
}[/highlight]
System.IO.Pipes.PipeStream
First let me explain what a pipe is… A pipe is basically an internal server created by a software program that allows other software programs (or even the same program) to communicate with it. Pipes can be isolated so a remote computer cannot access them. There are two different types of pipes:
- Anonymous – These pipes are only accessible between a parent and it’s child processes. A child process would be able to communicate with it’s parent process by the parent specifying the read and/or write handles for the pipe in some form (input/output handle, arguments, etc).
- Named – Unlike anonymous pipes, a named pipe is accessible by all the processes within the operating system and (if allowed) other remote computers (assuming they know the name of the pipe). The format for a named pipe is “\\ServerName\pipe\PipeName”. ServerName is the IP address or hostname of the computer with the pipe (if it is the local computer, it is “.”). The PipeName is the name of the pipe.
PipeStream is an abstract class, so it can’t be instantiated. Instead there are the following classes to use:
- AnonymousPipeServerStream – With this class, you can create an anonymous pipe server that clients can connect to using it’s handle. You’re also able to specify the pipe direction (in and/or out), if it can be inherited by a child process, and the buffer size.
- AnonymousPipeClientStream – This class is used to connect to an anonymous pipe server using the handle for it (as a string or SafePipeHandle). The direction can also be set to in and/or out and the remote computer to connect to.
- NamedPipeServerStream – A named pipe server can be created with this class. The name of the pipe is required and additional options (direction, maximum instances, transmission mode, etc.) can be set. By default, the named pipe will allow remote computers to connect to it but they can be disallowed by changing the transmission mode or denying access to the NT AUTHORITY\NETWORK user.
- NamedPipeClientStream – You can use this class to connect to a named pipe server. You need either the name of the pipe or a handle and can also specify the direction, access rights, remote computer to connect to and more.
The following example creates a named server pipe called “testpipe”, allows input and output, and a maximum of 4 server instances
[highlight lang=”cs”]NamedPipeServerStream pipeServer = new NamedPipeServerStream(“testpipe”, PipeDirection.InOut, 4);
StreamReader sr = new StreamReader(pipeServer);
StreamWriter sw = new StreamWriter(pipeServer);
while (true) {
pipeServer.WaitForConnection();
// Handle connection
pipeServer.WaitForPipeDrain();
if (pipeServer.IsConnected)
pipeServer.Disconnect();
}[/highlight]
A more closer look at using pipes (or named pipes, specifically) can be found in this CodeProject article.
System.Security.Cryptography.CryptoStream
This class is a helper class that takes in another Stream object. It is useful for encrypting or decrypting data (or in some cases where it is a one-way hashing algorithm, only encrypting). The way the one-way and two-way hashes work with CryptoStream is a bit different. With a two-way hash, the data will be encrypted as it is written and decrypted as it is read from the stream. However with a one-way hash, the hash is calculated from the data that is either read or written to the stream and the hash is not written to the stream.
There are many different two-way and one-way hashing algorithms that can be used from the System.Security.Cryptography namespace. Each algorithm has their own use case and are different or similar from one another. I suggest you find out more about the algorithms as it is something better to learn on your own.
The following is an example of encrypting and decrypting with Rijndael and calculating a hash with MD5.
[highlight lang=”cs”]byte[] text = new byte[] {0x41, 0x42, 0x43, 0x44}; // ABCD
RijndaelManaged twoWayAlgo = new RijndaelManaged();
using (CryptoStream cs1 = new CryptoStream(File.Create(“file1.dat”), twoWayAlgo.CreateEncryptor(), CryptoStreamMode.Write))
{
// Encrypts ABCD using Rijndael and writes it to the file
cs1.Write(text, 0, 4);
}
using (CryptoStream cs2 = new CryptoStream(File.OpenRead(“file1.dat”), twoWayAlgo.CreateDecryptor(), CryptoStreamMode.Read))
{
// Reads from the file and decrypts it to ABCD using Rijndael
cs2.Read(text, 0, 4);
}
MD5 oneWayAlgo = MD5.Create();
using (CryptoStream cs3 = new CryptoStream(File.Create(“file2.dat”), oneWayAlgo, CryptoStreamMode.Write))
{
cs3.Write(text, 0, text.Length);
cs3.FlushFinalBlock();
}
// Calculated hash
byte[] hash = oneWayAlgo.Hash;[/highlight]
Interested and want to find out more about CryptoStream? Check out this CodeProject article.
Helper Classes
Below is a list of helper classes that make it easier for reading or writing streams. I have included a brief description of what they do and more information about them can be found in the link to the MSDN website.
- StreamReader – Allows for a string to be read from the stream until the end of a line, end of the stream. As well, a char array can be read.
- StreamWriter – Various types of data (string, int, bool, char, etc) can be written to the stream by first converting them to their string representation. They may also have be written with a new line.
- BinaryReader – Data is read from the stream in it’s binary form. For example, a string is read by first getting the first byte which represents the length and then getting the bytes in the length to a string.
- BinaryWriter – Data is written to the stream in it’s binary form. For example, a string is written to by first writing the length as a byte and then the string as bytes.
Conclusion
Whatever you are developing in C#, you are likely to use a stream in some form or another. The best way to learn about these streams is by experimenting with them. The Microsoft Developer Network (MSDN) has quite a bit of details on how you can use streams. There are more ways of using streams and files that is also found on the MSDN website. Be sure to keep checking this blog for part 2 of C#.