only for RuBoard - do not distribute or recompile |
The FCL 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 FCL and provides numerous examples leveraging the predefined classes. The types mentioned in this section all exist in the System.Net 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 FCL 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 in which 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 (WebRequest) parses the URI and creates the appropriate protocol handler to fulfill the request.
Protocol handlers share a common abstract base type (WebRequest) that 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 (WebResponse) that exposes a Stream, providing simple streams-based I/O and easy integration into the rest of the FCL.
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 // 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 = WebRequest.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 WebRequest 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 FCL 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 // 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 = WebRequest.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 Web-Request.RegisterPrefix( ) 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 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 // 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.AcceptSocket( ); // 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 and repeat s.Send(barr, barr.Length, 0); s.Shutdown(SocketShutdown.Both); 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, 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 // 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); } }
only for RuBoard - do not distribute or recompile |