Lower-Level Protocols


This section briefly discusses some of the .NET classes used to communicate at a lower level.

Network communications work on several different levels. The classes you have seen in this chapter so far work at the highest level: the level at which specific commands are processed. It is probably easiest to understand this concept if you think of file transfer using FTP. Although today's GUI applications hide many of the FTP details, it was not so long ago when you executed FTP from a command-line prompt. In this environment you explicitly typed commands to send to the server for downloading, uploading, and listing files.

FTP is not the only high-level protocol relying on textual commands. HTTP, SMTP, POP, and other protocols are based on a similar type of behavior. Again, many of the modern graphical tools hide the transmission of commands from the user, so you are generally not aware of them. For example, when you type a URL into a Web browser, and the Web request goes off to a server, the browser is actually sending a (plain text) GET command to the server, which fulfills a similar purpose as the FTP get command. It can also send a POST command, which indicates that the browser has attached other data to the request.

However, these protocols are not sufficient by themselves to achieve communication between computers. Even if both the client and the server understand, for example, the HTTP protocol, it will still not be possible for them to understand each other unless there is also agreement on exactly how to transmit the characters: What binary format will be used? And getting down to the lowest level, what voltages will be used to represent 0s and 1s in the binary data? Because there are so many items to configure and agree upon, developers and hardware engineers in the networking field often refer to a protocol stack. When you list all of the various protocols and mechanisms required for communication between two hosts, you create a protocol stack with high-level protocols on the top and low-level protocols on the bottom. This approach results in a modular and layered approach to achieving efficient communication.

Luckily, for most development work, you don't need to go far down the stack or work with voltage levels, but if you are writing code that requires efficient communication between computers, it's not unusual to write code that works directly at the level of sending binary data packets between computers. This is the realm of protocols such as TCP, and Microsoft has supplied a number of classes that allow you to conveniently work with binary data at this level.

Lower-Level Classes

The System.Net.Sockets namespace contains the relevant classes. These classes, for example, allow you to directly send out TCP network requests or to listen to TCP network requests on a particular port. The following table explains the main classes.

Class

Purpose

Socket

Low-level class that deals with managing connections. Classes such as WebRequest, TcpClient, and UdpClient use this class internally.

NetworkStream

Derived from Stream. Represents a stream of data from the network.

TcpClient

Enables you to create and use TCP connections.

TcpListener

Enables you to listen for incoming TCP connection requests.

UdpClient

Enables you to create connections for UDP clients. (UDP is an alternative protocol to TCP, but is much less widely used, mostly on local networks.)

Using the TCP classes

The transmission control protocol (TCP) classes offer simple methods for connecting and sending data between two endpoints. An endpoint is the combination of an IP address and a port number. Existing protocols have well-defined port numbers, for example, HTTP uses port 80, while SMTP uses port 25. The Internet Assigned Number Authority, IANA, (http://www.iana.org/) assigns port numbers to these well-known services. Unless you are implementing a well-known service, you will want to select a port number above 1,024.

TCP traffic makes up the majority of traffic on the Internet today. TCP is often the protocol of choice because it offers guaranteed delivery, error correction, and buffering. The TcpClient class encapsulates a TCP connection and provides a number of properties to regulate the connection, including buffering, buffer size, and timeouts. Reading and writing is accomplished by requesting a NetworkStream object via the GetStream() method.

The TcpListener class listens for incoming TCP connections with the Start() method. When a connection request arrives, you can use the AcceptSocket() method to return a socket for communication with the remote machine, or use the AcceptTcpClient() method to use a higher-level TcpClient object for communication. The easiest way to demonstrate the TcpListener and TcpClient classes working together is to work through an example.

The TcpSend and TcpReceive examples

To demonstrate how these classes work you need to build two applications. Figure 35-13 shows the first application, TcpSend. This application opens a TCP connection to a server and sends the C# source code for itself.

image from book
Figure 35-13

Once again you create a C# Windows application. The form consists of two text boxes (txtHost and txtPort) for the host name and port, respectively, as well as a button (btnSend) to click and start a connection. First, you ensure that you include the relevant namespaces:

 using System.Net;  using System.Net.Sockets;  using System.IO; 

The following code shows the event handler for the button's click event:

 private void btnSend_Click(object sender, System.EventArgs e) { TcpClient tcpClient = new TcpClient(txtHost.Text, Int32.Parse(txtPort.Text)); NetworkStream ns = tcpClient.GetStream(); FileStream fs = File.Open("..\\..\\form1.cs", FileMode.Open); int data = fs.ReadByte(); while(data != -1) { ns.WriteByte((byte)data); data = fs.ReadByte(); } fs.Close(); ns.Close(); tcpClient.Close(); } 

This example creates the TcpClient using a host name and a port number. Alternatively, if you have an instance of the IPEndPoint class, you can pass the instance to the TcpClient constructor. After retrieving an instance of the NetworkStream class, you open the source code file and begin to read bytes. Like many of the binary streams, you need to check for the end of the stream by comparing the return value of the ReadByte() method to -1. After your loop has read all of the bytes and sent them along to the network stream, you make sure to close all of the open files, connections, and streams.

On the other side of the connection, the TcpReceive application displays the received file after the transmission is finished (see Figure 35-14).

image from book
Figure 35-14

ns.WriteByte((byte)data); data = fs.ReadByte(); 

The form consists of a single TextBox control, named txtDisplay. The TcpReceive application uses a TcpListener to wait for the incoming connection. To avoid freezing the application interface, you use a background thread to wait for and then read from the connection. Thus, you need to include the System.Threading namespace as well:

using System.Net; using System.Net.Sockets; using System.IO; using System.Threading; 

Inside the form's constructor, you spin up a background thread:

 public Form1() { InitializeComponent(); Thread thread = new Thread(new ThreadStart(Listen)); thread.Start(); } 

The remaining important code is this:

 public void Listen() { IPAddress localAddr = IPAddress.Parse("127.0.0.1"); Int32 port = 2112; TcpListener tcpListener = new TcpListener(localAddr, port); tcpListener.Start(); TcpClient tcpClient = tcpListener.AcceptTcpClient(); NetworkStream ns = tcpClient.GetStream(); StreamReader sr = new StreamReader(ns); string result = sr.ReadToEnd(); Invoke(new UpdateDisplayDelegate(UpdateDisplay),  new object[] {result} ); tcpClient.Close(); tcpListener.Stop(); } public void UpdateDisplay(string text) { txtDisplay.Text= text; } protected delegate void UpdateDisplayDelegate(string text); 

The thread begins execution in the Listen() method and allows you to make the blocking call to AcceptTcpClient() without halting the interface. Notice that the IP address (127.0.0.1) and the port number (2112) are hard-coded into the application, so you will need to enter the same port number from the client application.

You use the TcpClient object returned by AcceptTcpClient() to open a new stream for reading. Similar to the earlier example, you create a StreamReader to convert the incoming network data into a string. Before you close the client and stop the listener, you update the form's text box. You do not want to access the text box directly from your background thread, so you use the form's Invoke() method with a delegate and pass the result string as the first element in an array of object parameters. Invoke() ensures your call is correctly marshaled into the thread owning the control handles in the user interface.

TCP versus UDP

The other protocol covered in this section is UDP (user datagram protocol). UDP is a simple protocol with few features but also little overhead. Developers often use UDP in applications where the speed and performance requirements outweigh the reliability needs, for example, video streaming. In contrast, TCP offers a number of features to confirm the delivery of data. TCP provides error correction and retransmission in the case of lost or corrupted packets. Last, but hardly least, TCP buffers incoming and outgoing data and also guarantees a sequence of packets scrambled in transmission are reassembled before delivery to the application. Even with the extra overhead, TCP is the most widely used protocol across the Internet because of the higher reliability.

The UDP class

As you might expect, the UdpClient class features a smaller and simpler interface compared to TcpClient. This reflects the relatively simpler nature of the protocol in comparison to TCP. Though both TCP and UDP classes use a socket underneath the covers, the UdpClient client does not contain a method to return a network stream for reading and writing. Instead, the member function Send() accepts an array of bytes as a parameter, and the Receive() function returns an array of bytes. Also, Because UDP is a connectionless protocol, you can wait to specify the endpoint for the communication as a parameter to the Send() and Receive() methods, instead of earlier in a constructor or Connect() method. You can also change the endpoint on each subsequent send or receive.

The following code fragment uses the UdpClient class to send a message to an echo service. A server with an echo service running accepts TCP or UDP connections on port 7. The echo service simply echoes any data sent to the server back to the client. This service is useful for diagnostics and testing, although many system administrators disable echo services for security reasons:

 using System; using System.Text; using System.Net; using System.Net.Sockets; namespace Wrox.ProCSharp.InternetAccess.UdpExample { class Class1 { [STAThread] static void Main(string[] args) { UdpClient udpClient = new UdpClient(); string sendMsg = "Hello Echo Server"; byte [] sendBytes = Encoding.ASCII.GetBytes(sendMsg); udpClient.Send(sendBytes, sendBytes.Length, "SomeEchoServer.net", 7); IPEndPoint endPoint = new IPEndPoint(0,0); byte [] rcvBytes = udpClient.Receive(ref endPoint); string rcvMessage = Encoding.ASCII.GetString(rcvBytes, 0, rcvBytes.Length); // should print out "Hello Echo Server" Console.WriteLine(rcvMessage); } } } 

You make heavy use of the Encoding.ASCII class to translate strings into arrays of byte and vice versa. Also note that you pass an IPEndPoint by reference into the Receive() method. Because UDP is not a connection-oriented protocol, each call to Receive() might pick up data from a different endpoint, so Receive() populates this parameter with the IP address and port of the sending host.

Both UdpClient and TcpClient offer a layer of abstraction over the lowest of the low-level classes: the Socket.

The Socket class

The Socket class offers the highest level of control in network programming. One of the easiest ways to demonstrate the class is to rewrite the TcpReceive application with the Socket class. The updated Listen() method is listed in this example:

 public void Listen() { Socket listener = new Socket(AddressFamily.InterNetwork,  SocketType.Stream, ProtocolType.Tcp); listener.Bind(new IPEndPoint(IPAddress.Any, 2112)); listener.Listen(0); Socket socket = listener.Accept(); Stream netStream = new NetworkStream(socket); StreamReader reader = new StreamReader(netStream); string result = reader.ReadToEnd(); Invoke(new UpdateDisplayDelegate(UpdateDisplay),  new object[] {result} ); socket.Close(); listener.Close(); } 

The Socket class requires a few more lines of code to complete the same task. For starters, the constructor arguments need to specify an IP addressing scheme for a streaming socket with the TCP protocol. These arguments are just one of the many combinations available to the Socket class, and the TcpClient class configured these settings for you. You then bind the listener socket to a port and begin to listen for incoming connections. When an incoming request arrives you can use the Accept() method to create a new socket for handling the connection. You ultimately attach a StreamReader instance to the socket to read the incoming data, in much the same fashion as before.

The Socket class also contains a number of methods for asynchronously accepting, connecting, sending, and receiving. You can use these methods with callback delegates in the same way you used the asynchronous page requests with the WebRequest class. If you really need to dig into the internals of the socket, the GetSocketOption() and SetSocketOption() methods are available. These methods allow you to see and configure options, including timeout, time-to-live, and other low-level options.




Professional C# 2005
Pro Visual C++ 2005 for C# Developers
ISBN: 1590596080
EAN: 2147483647
Year: 2005
Pages: 351
Authors: Dean C. Wills

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net