|
Using the Socket-Level Classes
The socket-level classes in System.Net enable fine-grained control over the way data is read from and written to the network. These classes include access to sockets, a stream pattern for network I/O, and some helper classes that simplify a number of the most common tasks that are desirable when working with this level of the network. Figure 6-3 displays the groups of classes that make up the socket-level APIs for System.Net.
Figure 6-3: Socket-level classes in System.Net
Socket Classes
The System.Net.Sockets namespace contains a class-based representation of the Windows Sockets model commonly known as Winsock. These classes are particularly focused on a subset of the Winsock model that deals with IP-based protocols such as User Datagram Protocol (UDP) and Transmission Control Protocol (TCP). These classes can be extended to enable other protocols such as NetBIOS and Infrared. The key classes to be aware of at this level are Socket, SocketPermission, and SocketException. The Socket class provides the vast majority of the functionality needed to access the network and acts as the gatekeeper for the transition from managed .NET Framework code to the underlying native Win32 APIs. SocketException is used to represent most exceptions that occur within the Socket class. The SocketPermission class is used to control access to socket-level resources through code access security. These classes are described in more detail in Chapters 8 and 9. The following example demonstrates how to download a Web page using the socket-level classes. |
static void Main(string[ ] args)
{
// Validate the input values
if(args.Length < 2)
{
Console.WriteLine("Expected executable_file_name serverName path");
Console.WriteLine("Example: DownloadWebPage contoso.com /");
return;
}
string server = args[0];
string path = args[1];
int port = 80;
IPHostEntry host = null;
IPEndPoint remoteEndPoint = null;
Socket client = null;
// Resolve the server name
try
{
host = Dns.GetHostByName(server);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
return;
}
// Attempt to connect on each address returned from DNS.
// Break out once successfully connected
foreach(IPAddress address in host.AddressList)
{
try
{
client = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
remoteEndPoint = new IPEndPoint(address, port);
client.Connect(remoteEndPoint);
break;
}
catch(SocketException ex)
{
Console.WriteLine(ex.ToString());
}
}
// MakeRequest will issue the HTTP download request and write the server response to the console
MakeRequest(client, path, server);
client.Close();
}
public static void MakeRequest(Socket client, string path, string server)
{
// Format the HTTP GET request string
string Get = "GET " + path + " HTTP/1.0\r\nHost: " + server + "\r\nConnection: Close\r\n\r\n";
Byte[ ] ByteGet = Encoding.ASCII.GetBytes(Get);
// Send the GET request to the connected server
client.Send(ByteGet);
// Create a buffer that is used to read the response
byte[ ] responseData = new byte[1024];
// read the response and save the ASCII data in a string
int bytesRead = client.Receive(responseData);
StringBuilder responseString = new StringBuilder();
while (bytesRead != 0)
{
responseString.Append(Encoding.ASCII.GetChars(responseData), 0, bytesRead);
bytesRead = client.Receive(responseData);
}
// Display the response to the console
Console.WriteLine(responseString.ToString());
}
Sub Main(ByVal args As String())
' Validate the input values
If args.Length < 2 Then
Console.WriteLine("Expected executable_file_name serverName path")
Console.WriteLine("Example: DownloadWebPage contoso.com /")
Return
End If
Dim server As String = args(0)
Dim path As String = args(1)
Dim port As Integer = 80
Dim host As IPHostEntry
Dim remoteEndPoint As IPEndPoint
Dim client As Socket
' Resolve the server name
Try
host = Dns.GetHostByName(server)
Catch ex As Exception
Console.WriteLine(ex.ToString())
Return
End Try
' Attempt to connect on each address returned from DNS.
' Exit out once successfully connected
For Each address As IPAddress In host.AddressList
Try
client = New Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp)
remoteEndPoint = New IPEndPoint(address, port)
client.Connect(remoteEndPoint)
Exit For
Catch ex As Exception
Console.WriteLine(ex.ToString())
End Try
Next
' MakeRequest will issue the HTTP download request and write the server response to the console
MakeRequest(client, path, server)
client.Close()
End Sub
Sub MakeRequest(ByVal client As Socket, ByVal path As String,
ByVal server As String)
' Format the HTTP GET request string
Dim getString As String = "GET " + path + " HTTP/1.0" + ControlChars.CrLf
getString += "Host: " + server + ControlChars.CrLf + "Connection: Close"
getString += ControlChars.CrLf + ControlChars.CrLf
Dim ByteGet As Byte() = Encoding.ASCII.GetBytes(getString)
' Send the GET request to the connected server
client.Send(ByteGet)
' Create a buffer that is used to read the response
Dim responseData() As Byte = New Byte(1024) { }
' Read the response and save the ASCII data in a string
Dim bytesRead As Integer = client.Receive(responseData)
Dim responseString As StringBuilder = New StringBuilder
While bytesRead > 0
responseString.Append(Encoding.ASCII.GetChars(responseData), 0, bytesRead)
bytesRead = client.Receive(responseData)
End While
' Display the response to the console
Console.WriteLine(responseString.ToString())
End Sub
You’ll notice that the sample for downloading a Web page using sockets is much longer than the code it would take to do the same thing using a higher layer in System.Net, such as WebRequest or WebClient. This additional code is the tradeoff that’s often made when you decide to program at the socket level. Your application will have much greater control over the protocol that’s emitted, but it does so at the cost of additional code.
Although NetworkStream isn’t a large class, it’s extremely useful because it provides a stream-based model for reading and writing data over a network. NetworkStream implements the standard methods and properties that you’ll find on the base System.IO.Stream pattern, as discussed in Chapter 2. You should consider a couple of interesting elements that are specific to this implementation when working with NetworkStream. First, a NetworkStream instance can be obtained either by constructing one from a socket or by getting one from another class such as one of the socket-level helper classes described in the next section. Second, because NetworkStream is based on a TCP connection and does not buffer data, it’s not seekable.
You can’t set the Position property to seek a particular point in the stream. You should be aware of this limitation if you rely on other classes that use the Stream type and expect this functionality from the stream.
Using the Socket-Level Helper Classes
The socket-level helper classes include TcpClient, TcpListener, and UdpClient. These classes simplify the most common tasks that an application might perform using the TCP and UDP protocols. The TcpClient class is focused on the scenario of connecting to a remote host and getting a stream that can be used to read and write data. The TcpListener class is the inverse of TcpClient. It provides methods for accepting an incoming socket connection and accessing a stream that’s created on top of that connection. The UdpClient class contains methods for sending and receiving data over UDP, including JoinMulticastGroup and DropMulticastGroup for IP multicasting data. Because the UDP protocol is connectionless, no model is provided for creating a NetworkStream on top of it. Without the ability to create a stream, there’s little value to having a listener correspondent to the UdpClient class because the UdpClient.Receive method takes care of receiving data from a remote host. Because the socket-level helper classes do not include asynchronous methods, applications designed for high-load scenarios should use the Socket class instead. |
|
Create a new CLR console application and you can use SocketChap6CP as the project and solution names if you want.
Add the following code.
// SocketChap6CP.cpp : main project file. /// <summary> /// This sample demonstrates the socket classes /// being used to download a Web page that is passed in on the command line. /// </summary> #include "stdafx.h"
using namespace System; using namespace System::IO; using namespace System::Net; using namespace System::Text; using namespace System::Net::Sockets;
static void MakeRequest(Socket^ client, String^ path, String^ server) { // Format the HTTP GET request string Console::WriteLine("Formatting the HTTP GET request string..."); String^ Get = "GET " + path + " HTTP/1.0\r\nHost: " + server + "\r\nConnection: Close\r\n\r\n"; array<Byte>^ ByteGet = Encoding::ASCII->GetBytes(Get);
// Send the GET request to the connected server Console::WriteLine("Sending the GET request to the connected server..."); client->Send(ByteGet); Console::WriteLine("Send() is OK...");
// Create a buffer that is used to read the response Console::WriteLine("Creating a buffer that is used to read the response..."); array<Byte>^ responseData = gcnew array<Byte>(1024);
// Read the response and save the ASCII data in a string Console::WriteLine("Reading the response and save the ASCII data in a string..."); int bytesRead = client->Receive(responseData); Console::WriteLine("Receive() is OK..."); Console::WriteLine("Creating the StringBuilder and do the appending using Append()..."); StringBuilder^ responseString = gcnew StringBuilder(); while (bytesRead != 0) { responseString->Append(Encoding::ASCII->GetChars(responseData), 0, bytesRead); bytesRead = client->Receive(responseData); }
// Display the response to the console Console::WriteLine("Displaying the response to the console..."); Console::WriteLine(" " + "\"" + responseString->ToString() + "\""); } /// <summary> /// The main entry point for the application. /// </summary> [STAThread] int main(array<System::String ^> ^args) { // Validating the input values if (args->Length < 2) { Console::WriteLine("Usage: Executable_file_name serverName path"); Console::WriteLine("Example: Executable_file_name contoso.com /"); Console::WriteLine("Example: Executable_file_name youtube.com /"); // Return to OS return 0; }
// Else, if enough args supplied... // Store the first and second command line args Console::WriteLine("Reading & storing the first and second command line args..."); String^ server = args[0]; String^ path = args[1]; // change accordingly int port = 80;
IPHostEntry^ host = nullptr; IPEndPoint^ remoteEndPoint = nullptr; Socket^ client = nullptr;
// Resolve the server name try { Console::WriteLine("Resolving the server name..."); host = Dns::GetHostEntry(server); } catch (Exception^ ex) { Console::WriteLine("Error: " + ex->Message); return 0; }
// Attempt to connect on each address returned from DNS. Break out once successfully connected Console::WriteLine("Attempt to connect on each address returned from DNS..."); for each (IPAddress^ address in host->AddressList) { try { client = gcnew Socket(address->AddressFamily, SocketType::Stream, ProtocolType::Tcp); Console::WriteLine("Socket() is OK..."); Console::WriteLine("Creating the IPEndPoint..."); remoteEndPoint = gcnew IPEndPoint(address, port); client->Connect(remoteEndPoint); Console::WriteLine("Remote Connect() is OK, address: " + address + ", port: " + port); break; } catch (SocketException^ ex) { Console::WriteLine("Error: " + ex->Message); } }
// MakeRequest will issue the HTTP download request and write the server response to the console Console::WriteLine("Issuing the HTTP download request using MakeRequest()..."); MakeRequest(client, path, server); Console::WriteLine("MakeRequest() is OK..."); Console::WriteLine("Closing the client..."); client->Close();
return 0; } |
Build and run the project. The following are the output samples.