The .NET Framework includes two namespaces designed for network programming: System.Net and System.Net.Sockets. The System.Net namespace includes several classes that won't interest peer-to-peer programmers, including abstract base classes and types used to set Windows authentication credentials. However, there are also several noteworthy types:
IPAddress represents a numeric IP address.
IPEndPoint represents a combination of an IPAddress and port number. Taken together, these constitute a socket endpoint.
Dns provides shared helper methods that allow you to resolve domain names (for example, you can convert a domain name into a number IP address, and vice versa).
IPHostEntry associates a DNS host name with an array of IPAddress objects. Usually, you'll only be interested in retrieving the first IP address (in fact, in most cases there will only be one associated IP address).
The HttpWebRequest and HttpWebResponse classes are useful when downloading a web page from a web server. However, we won't use these classes in this book.
The System.Net.Sockets class includes the types you'll need for socket programming with TCP or UDP. This namespace holds the most important functionality for the peer-to-peer programmer, including the following class types:
TcpListener is used on the server side to listen for connections.
TcpClient is used on the server and client side to transfer information over a TCP connection. Usually, you'll transmit data by reading and writing to the stream returned from TcpClient.GetStream().
UdpClient is used on the server and client to transfer information over a UDP connection, using methods such as Send() and Receive().
Socket represents the Berkeley socket used by both TCP and UDP. You can communicate using this socket directly, but it's usually easier to use the higher-level TcpClient and UdpClient classes.
SocketException represents any error that occurs at the operating system level when attempting to establish a socket connection or send a message.
NetworkStream is used with TCP connections. It allows you to send and receive data using a convention .NET stream, which is quite handy.
In the remainder of this chapter, we'll consider some of these essential types and create a few sample programs that show networking in action.
On the Internet, publicly accessible IP addresses can be mapped to host names. For example, the IP address 184.108.40.206 maps to http://www.microsoft.com, and you can use either the domain name or the IP address when accessing the site in a browser.
In some cases, you'll need to retrieve the IP address for a domain name, or vice versa. This task is performed seamlessly in a web browser, and it can also be accomplished using the nslookup.exe command-line utility. In order to retrieve this information, your computer must access a DNS server. If the DNS server you contact cannot resolve the name by examining the values in its cache, it will forward the request to a DNS root server.
In .NET, you can perform this task quite easily using the Dns class, which provides a small set of shared methods. For example, the following code snippet retrieves an IPHostEntry for a specific domain name and then displays the first linked IP address.
Dim IP As IPHostEntry IP = Dns.GetHostByName("www.microsoft.com") ' Displays "220.127.116.11". Console.WriteLine(IP.AddressList(0).ToString())
The following code performs the reverse task:
Dim IP As IPHostEntry IP = Dns.GetHostByAddress("18.104.22.168") ' Displays "microsoft.com". Console.WriteLine(IP.HostName)
Finally, you can use the Dns.GetHostName() method to retrieve the host name of the current computer, which you can then convert into the local numeric IP address.
Dim IP As IPHostEntry IP = Dns.GetHostByName(Dns.GetHostName()) ' Displays the IP address for the current computer. Console.WriteLine(IP.AddressList(0).ToString())
In .NET, you can send data over a TCP connection using the NetworkStream class, which follows the standard .NET streaming model. That means that you can write data to a NetworkStream in the same way that you would write bytes to a file. (It also means you can chain a CryptoStream onto your network stream for automatic encryption.)
The NetworkStream class differs slightly from other .NET streams because it represents a buffer of data that's just about to be sent or has just been received. On the sender's side, the buffer is emptied as data is sent across the network. On the recipient's side, the buffer is emptied as data is read into the application.
In addition, the NetworkStream class adds a few useful properties:
Writeable and Readable indicate whether the NetworkStream supports write and read operations, respectively.
Socket contains a reference to the underlying socket that's being used for data transmission.
DataAvailable is a Boolean flag that's set to True when there's incoming data in the stream that you have not yet read.
The Write() and Read() methods allow you to copy byte arrays to and from the NetworkStream, but to simplify life you'll probably use the BinaryWriter and BinaryReader classes that are defined in the System.IO namespace. These classes can wrap any stream, and automatically convert common .NET types (such as strings, integers, and dates) into an array of bytes.
One good rule of thumb is to use the same approach for writing to a file as you do when reading it. For example, if you use the BinaryWriter to write data, use the BinaryReader to retrieve it, instead of the NetworkStream.Read() methods. This prevents you from introducing problems if you don't decode data the same way you encode it. For example, by default the BinaryWriter encodes data to binary using UTF-8 encoding. If you use Unicode to decode it, a problem could occur.
Keep in mind that when you read more than one byte at a time, the method will not return until all the data is read. For example, if you use BinaryReader.ReadString(), the method will not return until it reaches the end of the string.
The BinaryReader class also helps to add type safety to the NetworkStream. For example, if you use BinaryReader.ReadString() and the data in the stream doesn't correspond to a string, an exception will be thrown immediately.