The BCL includes a number of types that make accessing networked resources easy. Offering different levels of abstraction, these types allow an application to ignore much of the detail normally required to access networked resources, while retaining a high degree of control.
This section describes the core networking support in the BCL, and
provides numerous examples leveraging the predefined classes. The
types mentioned in this section all exist in the
System.Net.RegularExpressions
and
System.Net.Sockets
namespaces.
High-level access is performed using a set of types that implement a generic request/response architecture that is extensible to support new protocols. The implementation of this architecture in the BCL also includes HTTP-specific extensions to make interacting with web servers easy.
Should the application require lower-level access to the network, types exist to support the Transmission Control Protocol (TCP) and User Datagram Protocol (UDP). Finally, in situations where direct transport-level access is required, there are types that provide raw socket access.
The request/response architecture is based on Uniform Resource Indicator (URI) and stream I/O, follows the factory design pattern, and makes good use of abstract types and interfaces.
A factory type
(WebRequestFactory
) parses the URI
and creates the appropriate protocol handler to fulfill the request.
Protocol handlers share a common
abstract base type (WebRequest
), which exposes
properties that configure the request and methods used to retrieve
the response.
Responses are also represented as types and share a common abstract
base type (WebRequest
) which exposes a
NetworkStream
, providing simple streams-based I/O
and easy integration into the rest of the BCL.
This example is a simple implementation of the popular
Unix
snarf utility. It demonstrates the use of the
WebRequest
and WebResponse
classes to retrieve the contents of a URI and print them to the
console:
// Snarf.cs - compile with /r:System.Net.dll // Run Snarf.exe <http-uri> to retrieve a web page using System; using System.IO; using System.Net; using System.Text; class Snarf { static void Main(string[] args) { // Retrieve the data at the URL with an WebRequest ABC WebRequest req = WebRequestFactory.Create(args[0]); WebResponse resp = req.GetResponse( ); // Read in the data, performing ASCII->Unicode encoding Stream s = resp.GetResponseStream( ); StreamReader sr = new StreamReader(s, Encoding.ASCII); string doc = sr.ReadToEnd( ); Console.WriteLine(doc); // Print result to console } }
The request/response architecture inherently supports protocol-specific extensions via the use of subtyping.
Since the WebRequestFactory
creates and returns
the appropriate handler type based on the URI, accessing
protocol-specific features is as easy as downcasting the returned
WebRequest
object to the appropriate
protocol-specific handler and accessing the extended functionality.
The BCL includes specific support for the HTTP protocol, including the ability to easily access and control elements of an interactive web session, such as the HTTP headers, user-agent strings, proxy support, user credentials, authentication, keep-alives, pipelining, and more.
This example demonstrates the use of the HTTP-specific request/response classes to control the user-agent string for the request and retrieve the server type:
// ProbeSvr.cs - compile with /r:System.Net.dll // Run ProbeSvr.exe <servername> to retrieve the server type using System; using System.Net; class ProbeSvr { static void Main(string[] args) { // Get instance of WebRequest ABC, convert to HttpWebRequest WebRequest req = WebRequestFactory.Create(args[0]); HttpWebRequest httpReq = (HttpWebRequest)req; // Access HTTP-specific features such as User-Agent httpReq.UserAgent = "CSPRProbe/1.0"; // Retrieve response and print to console WebResponse resp = req.GetResponse( ); HttpWebResponse httpResp = (HttpWebResponse)resp; Console.WriteLine(httpResp.Server); } }
Adding
handlers to support new protocols
is trivial: simply implement a new set of derived types based on
WebRequest
and WebResponse
,
implement the
IWebRequestCreate
interface on your WebRequest
-derived type, and
register it as a new protocol handler with the
Web-RequestFactory
at runtime. Once this is done,
any code that uses the request/response architecture can access
networked resources using the new URI format (and underlying
protocol).
The
System.Net.Sockets
namespace
includes types that provide protocol-level support for TCP and UDP.
These types are built on the underlying
Socket
type, which is itself directly accessible for transport-level access
to the network.
Two classes provide the TCP support:
TCPListener
and TCPClient
.
TCPListener
listens for incoming connections,
creating Socket
instances that respond to the
connection request. TCPClient
connects to a remote
host, hiding the details of the underlying socket in a
Stream
-derived type that allows stream I/O over
the network.
A class called UDPClient
provides the UDP support.
UDPClient
serves as both a client and a listener
and includes multicast support, allowing individual datagrams to be
sent and received as byte arrays.
Both the TCP and the UDP classes help to access the underlying
network socket (represented by the Socket
class).
The Socket
class is a thin wrapper over the native
Windows sockets functionality and is the lowest level of networking
accessible to managed code.
The following example is a simple implementation of the Quote of the
Day (QUOTD) protocol, as defined by the IETF in RFC 865. It
demonstrates the use of a TCP listener to accept incoming requests
and the use of the lower-level Socket
type to
fulfill the request:
// QOTDListener.cs - compile with /r:System.Net.dll // Run QOTDListener.exe to service incoming QOTD requests using System; using System.Net; using System.Net.Sockets; using System.Text; class QOTDListener { static string[] quotes = {@"Sufficiently advanced magic is indistinguishable from technology -- Terry Pratchett", @"Sufficiently advanced technology is indistinguishable from magic -- Arthur C Clarke" }; static void Main( ) { // Start a TCP listener on port 17 TCPListener l = new TCPListener(17); l.Start( ); Console.WriteLine("Waiting for clients to connect"); Console.WriteLine("Press Ctrl+C to quit..."); int numServed = 1; while (true) { // Block waiting for an incoming socket connect request Socket s = l.Accept( ); // Encode alternating quotes as bytes for sending Char[] carr = quotes[numServed%2].ToCharArray( ); Byte[] barr = Encoding.ASCII.GetBytes(carr); // Return data to client, then clean up socket & repeat s.Send(barr, barr.Length, 0); s.Shutdown(SocketShutdown.SdBoth); s.Close( ); Console.WriteLine("{0} quotes served...", numServed++); } } }
To test this example, run the listener and try connecting to port 17
on localhost using a telnet
client. (Under Windows 2000, this can be done from the command line
by entering: telnet localhost 17
).
Notice the use of Socket.Shutdown
and
Socket.Close
at the end of the
while
loop; this is required to flush and close
the socket immediately, rather than wait for the garbage collector to
finalize and collect unreachable Socket
objects
later.
The networking types in the base class library also support normal and reverse Domain Name System (DNS) resolution. Here’s an example using these types:
// DNSLookup.cs - compile with /r:System.Net.dll // Run DNSLookup.exe <servername> to determine IP addresses using System; using System.Net; class DNSLookup { static void Main(string[] args) { IPHostEntry he = DNS.GetHostByName(args[0]); IPAddress[] addrs = he.AddressList; foreach (IPAddress addr in addrs) Console.WriteLine(addr); } }
18.225.149.238