CSC 469/569 .NET TCP Programming Lab

IP Addresses

The IPAddress class is used to represent IP addresses. The following static method and field are useful:

  1. IPAddress Parse(String str) : parses a string representing an IP address using dotted decimal notation and returns and IPAddress object.
  2. IPAddress.Any: Use this field when you want to tell a TCP server socket (called a TcpListener in .NET parlance) to listen for client connection requests on all network interfaces.

TcpListener

Instead of a ServerSocket as in Java, the NET platform has the TcpListener class. The TcpListener is used to listen for connection requests from TcpClients. To use these classes, you need to know about the their constructors, methods, fields, and properties. We begin with the TcpListener class methods, fields, etc.

  1. public TcpListener(IPAddress localaddr, int port): This constructor creates a listener that watches for connection requests at the specified IP address and port number.
  2. public void Start() : This TcpListener method is called after the TcpListener has been created to maket it start listening for connection requests.
  3. public TcpClient AcceptTcpClient(): This is a method of the TcpListener class. It blocks waiting for a connection request from a TcpClient. When a connection request arrives and is accepted, the method returns a a TcpClient on the server side. The returned TcpClient is connected to the TcpClient that sent the connection request. Call this method after you have called Start().
  4. public void Stop() : Call this TcpListener method to stop the listener.

TcpClient

The TcpClient objects do the actual communication work.
  1. public TcpClient(string hostname, int port) : This constructor creates a TcpClient connected to the remote TcpListener running at the specified hostname and port. You can use an IP address string in the form of dotted decimal notation in place of the host name.
  2. public int Available { get; } : This property returns the number of bytes that have arrived at the TcpClient through the network and are available to be read.
  3. public void Close(): closes the TCP connection.
  4. public NetworkStream GetStream(): Call this method on a TcpClient after it is connected. The method returns a NetworkStream. You transfer data through the network by reading and writing this network stream object.

NetworkStream, Stream, and Reader and Writer classes

NetworkStream is a subclass of the Stream class. The best way to use NetworkStream is through other classes that are called readers and writers. Readers and writers are like Filter streams in Java: they wrap other streams and add convenient methods for reading and writing. You can learn more about readers and writers here. StreamReader and StreamWriter are used to read and write character streams, while BinaryReader and BinaryWriter are used to read and write byte streams. BinaryReader and BinaryWriter are similar to the DataInputStream and DataOutputStream in Java.

StreamReader

  1. public StreamReader(Stream stream) : Wrap an underlying stream so you can read it.
  2. public override string ReadLine(): Read and return the next line from the stream as a string. Returns null at the end of the stream.

StreamWriter

StreamWriter is a lot like PrintStream and PrintWriter in Java. There are Write() methods that work like print(), and WriteLine() methods that work like println(). Here is just a couple of methods from this class; you can look up the rest on your own.

  1. public StreamWriter(Stream stream) : Use this constructor to wrap a stream you want to write.
  2. bool AutoFlush { get; set; } Set this property to true to automatically flush the writer whenever you write a new line character.
  3. void Flush() : Manually flush the writer.
  4. void WriteLine(string value) : write a string to the stream.

Namespaces and .NET Programming

The .NET platform uses the concept of namespaces in much the same way that Java uses packages. Names of classes and other types are grouped in namespaces to avoid name collisions. When you look up the documentation for a class, you should note the namespaces that the class is defined in.

As an example, the TcpListener class is in the System.Net.Sockets namespace. To make use of it in your program, you need to include the using statement

    using System.Net.Sockets;

An example TCP server

The following is a simple server that echoes back in uppercase whatever it receives from the client. It terminates the connection when it gets a "bye" command from the client.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace TcpIntro
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create the TcpListener
            TcpListener listener = new TcpListener(IPAddress.Any, 50000);

            //Start the listener
            listener.Start();
            // Loop waiting for up to 5 connection requests from TcpClients
            for (int count = 1; count <= 5; count++)
            {
                Console.WriteLine("Waiting for a connection request");
                TcpClient client = listener.AcceptTcpClient();
                Console.WriteLine("Got a connection request");
                talkEchoToClient(client);
            }

        }

        static void talkEchoToClient(TcpClient client)
        {
            // Get NetworkStream from client
            NetworkStream netStream = client.GetStream();

            // Put a StreamReader  and a StreamWriter on the networkStream
            StreamReader reader = new StreamReader(netStream);
            StreamWriter writer = new StreamWriter(netStream);
            writer.AutoFlush = true;

            String input = reader.ReadLine();
            while (true)
            {
                // Echo upper-cased version of what you got
                writer.WriteLine(input.ToUpper());
                // Read another line from the client.
                input = reader.ReadLine();
                if (input == "bye") break;
            }
            client.Close();
        }
    }
}

Exercise A: Writing a TCP Client

First, a few words about Visual Studio. Visual Studio uses the concept of a Solution: a solution is a collection of related projects. Typically, when you start a project, it is placed in a solution with the same name as the project. Below you can see the Visual Studio Solution Explorer pane. It shows a solution TcpIntro that contains a single project, also called TcpIntro.

One other note: you need to include using statements for the namespaces that contain the classes you are using. You can look up the documentation on the MSDN sites for the class you are using: that documentation will tell you the namespace the class belongs to. Once you know the namespace, you can manually include the using statement at the top of the file. Alternatively, you can simpley type the name of the class and wait for Visual Studio to flag the class. Then, next to the class name, right click and select the Resolve ... item. Select the appropriate namespace and Visual Studio will add the corresponding namespace for you.

You should now copy this code into a Visual Studio project called TcpIntro, run the application, and test it using PUTTY.

TCP Clients

We are now going to add a project containing a TcPClient to the Solution. Right-click on the Solution node in the Solution Explorer, and make the chain of selections Add/New Project...

In the ensuing Add New Project dialog, select Console Application, and name the project MyTcpClient. You should get a shell for a project that you will expand to write the client code to connect to the TcpListener. Your client will be in a loop asking the human user to enter a string that it sends to the server. Remember that the server disconnect after receiving 5 messages from the client, or after receiving a "bye". Your client should ask for up to 5 messages from the user, any of which may be the "bye" message. A DOS command box showing the client program interacting with the server and it user follows.

To write the client, you will need to

  1. Use the various Read, ReadLine, Write, and WriteLine methods of System.Console class to read input from the keyboard and print output to the screen.
  2. Create a TcpClient connected to the TcpListener
  3. Grab a NetworkStream from the TcpClient and grab StreamWriter and a StreamReader from the NetworkStream object.
  4. Use the reader and writer objects to perform network communication with the server.

Running the Client and Server from Visual Studio

If your Visual Studio solution contains more than one project, you can right-click on the Solution node and Select the menu item Set Startup projects....

The Startup project is the one that runs when you use the Start button on the ToolBox just below the main menu. Ordinarily the first project created is the Startup project, so the Server will run when you use the Start button. To run the client, right click on the project node (MyTcpClient) and make the sequence of selections Debug/Start New Instance ....

Threads on the .NET Platform

Servers in a client-server networked application need to be multi-threaded to ensure good response times for connecting clients. We will now take a quick look at the .NET platforms.

The .NET Platform has a Thread class. To use this class, you need to know about some of its constructors and methods:

  1. Thread(ThreadStart startMethod): This constructor takes a ThreadStart object as parameter. The ThreadStart type is an object that wraps a method that takes no parameters and returns no value, that is, a method whose signature is void aMethod().
  2. Thread(ParametrizedThreadStart startMethod): This constructor takes a ParametrizedThreadStart object as parameter. The ParametrizedThreadStart type is an object that wraps a method that takes a single parameter of type Object and returns no value, that is, a method whose signature is void aMethod(Object ob).
  3. void Start(): Call this method on a thread created with a ThreadStart object to invoke the start method and start running the thread.
  4. void Start(Object ob): Call this method on a thread created with a ParametrizedThreadStart object to to pass the object ob to the start method and start running the thread.
  5. static void Sleep(int milliseconds): You can call this method in a thread function to pause execution of the thead for a specified number of milliseconds.
  6. void Join(): block the calling thread until the thread on which Join is called terminates. For example, if you have a thread th1, calling th1.Join() causes the calling thread to block until th1 terminates.

To illustrate, we will create a program with three threads. Each thread will print the same letter 50 times, waiting a random number of milliseconds after each letter has been printed. On thread will print the letter A, and the other threads will print the letters B and C, respectively. Once the three threads are started, the main thread will print the letter D 50 times. When the main thread gets done, it will call Join() on all the other threads. After that, the program terminates.

Right-click on the Solution node, and select the option to add a new project called ThreadExample. Edit the code for the project as shown below.

namespace ThreadExample
{
    class Program
    {
        // Random number generator used to generate random intervals
        static Random randy = new Random();

        // Number of times each thread will write a letter
        static int Count = 50;

        // This is the thread method for a thread that prints the letter 'A'
        static void MyAThreadMethod()
        {
            for (int k = 0; k < Count; k++)
            {
                System.Console.Write('A');
                Thread.Sleep(randy.Next(100));
            }
        }

        // This is a thread method for a thread that prints a letter 
        // passed in as parameter when Start is called on the thread
        static void MyParametrizedThreadMethod(Object ch)
        {
            // Cast the object ch to the type you know it should be.
            char ch1 = (char)ch;

            // Print the character  passed in as parameter
            for (int k = 0; k < Count; k++)
            {
                System.Console.Write(ch1);
                Thread.Sleep(randy.Next(100));
            }
        }

        static void Main(string[] args)
        {
            // Create a thread to print the letter A.
            Thread aThread = new Thread(MyAThreadMethod);

            // Create a thread to print a letter that will be passed 
            //in as parameter when the thread is started.
            Thread bThread = new Thread(MyParametrizedThreadMethod);

            // Create a thread to print a letter that will be passed 
            //in as parameter when the thread is started.
            Thread cThread = new Thread(MyParametrizedThreadMethod);

            // Start the thread to print A
            aThread.Start();
            // Start the thread to print B
            bThread.Start('B');
            // Start the thread to print C
            cThread.Start('C');

            // The Main thread will print  D
            for (int k = 0; k < Count; k++)
            {
                System.Console.Write('D');
                Thread.Sleep(randy.Next(100));
            }
           
            // If Main thread terminates first, it will wait for all other 
            // threads to terminate.
            aThread.Join();
            bThread.Join();
            cThread.Join();

            Console.WriteLine("Press any key to terminate");
            Console.ReadKey();
        }       
    }
}
        

Here is what the output should look like:

Exercise B: Writing a Multi-Threaded Server

Modify the echo server that was previously written to make it multi-threaded. The simplest way to do it is to write a ParametrizedStart method that takes a TcpClient as parameter. Note that the type of parameter for the thread method has to be Object, but then you have to cast it to TcpClient inside the method.

Test your multi-threaded server by simultaneously connecting to it with multiple clients.

Homework 6: A Multi-Threaded MapServer on the .NET Platform

This lab will not be graded. However, it is meant to help you with the skills needed to write a .NET version of the TCP multi-threaded map server. Homework 6 will be posted Friday night.