multi-client/server gui application. overview as part of the tcp

36
Multi-Client/Server GUI Application

Upload: rudolph-ramsey

Post on 03-Jan-2016

225 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Multi-Client/Server GUI Application.  Overview As part of the TCP

Multi-Client/ServerGUI Application

Page 2: Multi-Client/Server GUI Application.  Overview As part of the TCP

http://www.geekpedia.com/tutorial239_Csharp-Chat-Part-1---Building-the-Chat-Client.html

Overview

As part of the TCP Client-Server Laboratory Project you are to build a GUI (Form) client-server in which the IP address of the server can be specified by the user interactively through the Form. In this lecture we will review the development of Windows Form client-server applications based on tutorials by Andrew Pociu and presented in geekpedia.

The principal features of these apps are:

Clients can specify the IP address of the server.

Multiple clients can connect to the same server.

Each client can specify a name to be placed at the front of sent messages.

All connected clients can see messages sent from every client.

Multiple clients can send messages to the server concurrently.

Page 3: Multi-Client/Server GUI Application.  Overview As part of the TCP

Building the Client

txtIP

txtUser

btnConnect

btnSend

txtLog

txtSend

Page 4: Multi-Client/Server GUI Application.  Overview As part of the TCP

using System.Net;

using System.Net.Sockets;

using System.IO;

using System.Threading;

Since we will be making use of networking, streaming and threading objects, start by adding the following using statements:

Referencing the Standard Classes

Page 5: Multi-Client/Server GUI Application.  Overview As part of the TCP

// Will hold the user nameprivate string UserName = "Unknown";private StreamWriter swSender;private StreamReader srReceiver;private TcpClient tcpServer;

// Needed to update the form with messages from another threadprivate delegate void UpdateLogCallback(string strMessage);

// Needed to set the form to a "disconnected" state from another threadprivate delegate void CloseConnectionCallback(string strReason);private Thread thrMessaging;private IPAddress ipAddr;private bool Connected;

Declaring Objects

Most of our objects can be private since we will not be using them in another class. Our object list includes

(1) a stream reader and writer to send and receive messages,

(2) a TcpClient object to connect to the server,

(3) a delegate to be able to update the form with messages from another thread,

(4) another delegate to be able to disconnect the form from another thread, and

(5) objects to hold the UserName and ipAddr.

Page 6: Multi-Client/Server GUI Application.  Overview As part of the TCP

The btnConnect Click Event

private void btnConnect_Click(object sender, EventArgs e){    // If we are not currently connected but awaiting to connect    if (Connected == false)    {        // Initialize the connection        InitializeConnection();    }    else // We are connected, thus disconnect    {        CloseConnection("Disconnected at user's request.");    }}

What happen's when you click the btnConnect button?

What happen's when you click it again?

Page 7: Multi-Client/Server GUI Application.  Overview As part of the TCP

private void InitializeConnection()

{

    ipAddr = IPAddress.Parse(txtIp.Text); //convert IP text to object

    tcpServer = new TcpClient(); // Start TCP connections to server

    tcpServer.Connect(ipAddr, 1986);

    Connected = true;     // track whether we are connected or not    UserName = txtUser.Text; // Prepare the form.

    txtIp.Enabled = false; // Disable & enable appropriate fields

    txtUser.Enabled = false;

    txtMessage.Enabled = true;

    btnSend.Enabled = true;

    btnConnect.Text = "Disconnect";

    // Send the desired username to the server

    swSender = new StreamWriter(tcpServer.GetStream());

    swSender.WriteLine(txtUser.Text);

    swSender.Flush();

    // Start the thread for receiving messages and further communication

    thrMessaging = new Thread(new ThreadStart(ReceiveMessages));

    thrMessaging.Start();

}

The InitializeConnection( ) Method

Page 8: Multi-Client/Server GUI Application.  Overview As part of the TCP

private void ReceiveMessages(){    srReceiver = new StreamReader(tcpServer.GetStream()); // from server    string ConResponse = srReceiver.ReadLine();    if (ConResponse[0] == '1') // if 1st char = 1 connect was successful    {        // Update the form to tell it we are now connected        this.Invoke(new UpdateLogCallback(this.UpdateLog), new object[] { "Connected Successfully!" });    }    else // if 1st char != 1, the connection was unsuccessful    {        string Reason = "Not Connected: "; // reason starts at 3rd char        Reason += ConResponse.Substring(2, ConResponse.Length - 2);        this.Invoke(new CloseConnectionCallback(this.CloseConnection), new object[] { Reason });        return; // exit the method    }    while (Connected) // while connected read incoming lines from server    {        // Show the messages in the log TextBox        this.Invoke(new UpdateLogCallback(this.UpdateLog), new object[] { srReceiver.ReadLine() });    }}

ReceiveMessages( ) Method

Page 9: Multi-Client/Server GUI Application.  Overview As part of the TCP

more on ReceiveMessages( )

The this.Invoke( ) calls tell the form to update itself. We can't directly update the form elements ourselves from this method because it's in a separate thread (remember we called it using ThreadStart( )) and cross-thread operations are illegal.

Finally, the while (Connected) loop keeps calling the srReceiver.ReadLine( ) method which checks for incoming messages from the server. Next comes the method that we kept calling using this.Invoke() - all it does is to update the txtLog TextBox with the latest message:

// called from a different thread in order to update the log TextBoxprivate void UpdateLog(string strMessage){    // Append text also scrolls the TextBox to the bottom each time    txtLog.AppendText(strMessage + "\r\n");}

Page 10: Multi-Client/Server GUI Application.  Overview As part of the TCP

So far we've seen how to receive messages from the server, but nothing about how to send them. When do we want to send a message? When the Send button is clicked or when the Enter key is pressed while txtMessage has the focus. This should be hooked up to the Click event of the btnSend button:

// We want to send the message when the Send button is clickedprivate void btnSend_Click(object sender, EventArgs e){    SendMessage();}

And this needs to be hooked up to the KeyPress event of txtMessage:

// But we also want to send the message once Enter is pressedprivate void txtMessage_KeyPress(object sender, KeyPressEventArgs e){    // If the key is Enter     if (e.KeyChar == (char)13)    {        SendMessage();    }}

Two Ways to Send a Message

Page 11: Multi-Client/Server GUI Application.  Overview As part of the TCP

Both btnSend and a Carriage Return invokes the SendMessage( ) method.

// Sends the message typed in to the serverprivate void SendMessage(){    if (txtMessage.Lines.Length >= 1)    {        swSender.WriteLine(txtMessage.Text);        swSender.Flush();        txtMessage.Lines = null;    }    txtMessage.Text = "";}

SendMessage( ) Method

Page 12: Multi-Client/Server GUI Application.  Overview As part of the TCP

// Closes a current connectionprivate void CloseConnection(string Reason){    // Show the reason why the connection is ending    txtLog.AppendText(Reason + "\r\n");    // Enable and disable the appropriate controls on the form    txtIp.Enabled = true;    txtUser.Enabled = true;    txtMessage.Enabled = false;    btnSend.Enabled = false;    btnConnect.Text = "Connect";     // Close the objects    Connected = false;    swSender.Close();    srReceiver.Close();    tcpServer.Close();}

CloseConnection( ) Method

Page 13: Multi-Client/Server GUI Application.  Overview As part of the TCP

public Form1(){    // On application exit, don't forget to disconnect first    Application.ApplicationExit += new EventHandler(OnApplicationExit);    InitializeComponent();}

// The event handler for application exitpublic void OnApplicationExit(object sender, EventArgs e){    if (Connected == true)    {        // Closes the connections, streams, etc.        Connected = false;        swSender.Close();        srReceiver.Close();        tcpServer.Close();    }}

An Event Handler is Needed for the Form

This completes the Chat Client...

Page 14: Multi-Client/Server GUI Application.  Overview As part of the TCP

http://www.geekpedia.com/tutorial240_Csharp-Chat-Part-2---Building-the-Chat-Server.html

Building the Chat Server

The Chat Server application will hold information on all the connected clients, await for messages from each and send incoming messages to all. Since the server does not initiate messages there is no need for a txtMessage textbox or Send Button.

txtIP

txtLog

btnListen

Page 15: Multi-Client/Server GUI Application.  Overview As part of the TCP

using System.Net;

using System.Net.Sockets;

using System.IO;

using System.Threading;

As with the Chat Client, the Server will be making use of networking, streaming and threading objects, start by adding the following using statements:

Referencing the Standard Classes

Page 16: Multi-Client/Server GUI Application.  Overview As part of the TCP

private delegate void UpdateStatusCallback(string strMessage);

For the Chat Server we need to create only one object, a delegate to handle the messages to and from the Clients. This will be used to update the txtLog TextBox from another thread :

Declaring Object(s)

Page 17: Multi-Client/Server GUI Application.  Overview As part of the TCP

private void btnListen_Click(object sender, EventArgs e){    // Parse the server's IP address out of the TextBox    IPAddress ipAddr = IPAddress.Parse(txtIp.Text);

    // Create a new instance of the ChatServer object    ChatServer mainServer = new ChatServer(ipAddr);

    // Hook the StatusChanged event handler to mainServer_StatusChanged    ChatServer.StatusChanged += new StatusChangedEventHandler(mainServer_StatusChanged);

    // Start listening for connections    mainServer.StartListening();

    // Show that we started to listen for connections    txtLog.AppendText("Monitoring for connections...\r\n");}

The btnListen_Click( ) Method

Page 18: Multi-Client/Server GUI Application.  Overview As part of the TCP

public void mainServer_StatusChanged(object sender, StatusChangedEventArgs e){    // Call the method that updates the form    this.Invoke(new UpdateStatusCallback(this.UpdateStatus), new object[] { e.EventMessage });} private void UpdateStatus(string strMessage){    // Updates the log with the message    txtLog.AppendText(strMessage + "\r\n");}

mainServer_StatusChange( ) & UpdateStatus( )

In the btnListen_Click( ) method we instantiated a new StatusChanged event handler. This method, named mainServer_StatusChanged( ) calls the method that updates the Chat Server form. The method UpdateStatus( ), which is called by mainServer_ StatusChanged( ) actually changes the contents of the txtLog textbox.

This completes the Chat Server Form1 code...

Page 19: Multi-Client/Server GUI Application.  Overview As part of the TCP

The ChatServer.cs Class

Now we have to create a separate class. This is done within the .NET IDE in the followig manner:

(1) Inside the Solution Explorer panel, right-click the Solution 'ChatServer' 1 Project (or whatever name you have given your project).

(2) Select Add..New Item in the drop-down menu and sub-menu that appears

(3) Choose Visual C# Class from the list of types of objects and name it ChatServer.cs (same name as project is OK).

(4) Click the Add Button to create the shell for this new class.

Page 20: Multi-Client/Server GUI Application.  Overview As part of the TCP

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

This class needs the following references to System defined classes included in its project header.

Referencing the Standard Classes

Page 21: Multi-Client/Server GUI Application.  Overview As part of the TCP

// Holds the arguments for the StatusChanged eventpublic class StatusChangedEventArgs : EventArgs{    // The argument we're interested in is a message describing the event    private string EventMsg;     // Property for retrieving and setting the event message    public string EventMessage    {        get        {            return EventMsg;        }        set        {            EventMsg = value;        }    }     // Constructor for setting the event message    public StatusChangedEventArgs(string strEventMsg)    {        EventMsg = strEventMsg;    }}

Get( ) & Set( ) for the Event Arguments

Page 22: Multi-Client/Server GUI Application.  Overview As part of the TCP

// delegate needed to specify parameters we're passing with event

public delegate void StatusChangedEventHandler(object sender,

StatusChangedEventArgs e);

Delegate for the Event Handler

Page 23: Multi-Client/Server GUI Application.  Overview As part of the TCP

class ChatServer{    // This hash table stores users and connections (browsable by user)    public static Hashtable htUsers = new Hashtable(30); // 30 users max

    // hash table stores connections and users (browsable by connection)    public static Hashtable htConnections = new Hashtable(30); // 30 users max

    private IPAddress ipAddress; //stores IP address passed to it    private TcpClient tcpClient;    // this event notifies the form when a user connects, disconnects, etc..    public static event StatusChangedEventHandler StatusChanged;    private static StatusChangedEventArgs e;     // constructor sets IP address to one retrieved by instantiating object    public ChatServer(IPAddress address)    {        ipAddress = address;    }    private Thread thrListener; //thread to hold the connection listener    private TcpListener tlsClient; //TCP listening object    bool ServRunning = false; //tells while loop to continue monitoring 

The ChatServer Class

There are two hash tables defined for the client list because we will need to search by both connections and by users at different times in the operation of the application.

Page 24: Multi-Client/Server GUI Application.  Overview As part of the TCP

    // Add the user to the hash tables    public static void AddUser(TcpClient tcpUser, string strUsername)    {        // first add username and associated connection to hash tables        ChatServer.htUsers.Add(strUsername, tcpUser);        ChatServer.htConnections.Add(tcpUser, strUsername);         // report new connection to all other users and to server form        SendAdminMessage(htConnections[tcpUser] + " has joined us");    }     // remove this user from the hash tables    public static void RemoveUser(TcpClient tcpUser)    {        if (htConnections[tcpUser] != null) //if user is there        {            // show the info and tell the other users about disconnection            SendAdminMessage(htConnections[tcpUser] + " has left us");             // Remove the user from the hash table            ChatServer.htUsers.Remove(ChatServer.htConnections[tcpUser]);            ChatServer.htConnections.Remove(tcpUser);        }    } 

AddUser( ) & RemoveUser( ) Methods

The AddUser() method adds a new user to the hash tables, and therefore to our list of connected chat clients. The RemoveUser() method does the opposite.

Page 25: Multi-Client/Server GUI Application.  Overview As part of the TCP

    // this is called to raise the StatusChanged event    public static void OnStatusChanged(StatusChangedEventArgs e)    {        StatusChangedEventHandler statusHandler = StatusChanged;        if (statusHandler != null)        {            // Invoke the delegate            statusHandler(null, e);        }    }

OnStatusChanged( )

The OnStatusChanged will fire the StatusChanged event, which is handled inside Form1.cs. This is a way of updating the form with the latest message from inside the ChatServer object.

Page 26: Multi-Client/Server GUI Application.  Overview As part of the TCP

        for (int i = 0; i < tcpClients.Length; i++) //loop through client list        {            try //try sending a message to each            {                if (Message.Trim() == "" || tcpClients[i] == null)                {                    continue; //if message blank or connection null, break out                }                swSenderSender = new StreamWriter(tcpClients[i].GetStream());                swSenderSender.WriteLine("Administrator: " + Message);                swSenderSender.Flush();                swSenderSender = null; //send message to current user in loop            }            catch // if problem, the user is gone, remove him            {                RemoveUser(tcpClients[i]);            }        }    }

Send AdminMessage( )

// Send administrative messages    public static void SendAdminMessage(string Message)    {        StreamWriter swSenderSender;        // first, show in our application who says what        e = new StatusChangedEventArgs("Administrator: " + Message);        OnStatusChanged(e);         // create an array of TCP clients, size = num of users we have        TcpClient[] tcpClients = new TcpClient[ChatServer.htUsers.Count];        // copy the TcpClient objects into the array        ChatServer.htUsers.Values.CopyTo(tcpClients, 0);

. The SendAdminMessage sends an administrative message to all connected clients, by looping through the hash table, attempting to send each the message. If the message doesn't get through, they are assumed to be disconnected and they are removed.

Page 27: Multi-Client/Server GUI Application.  Overview As part of the TCP

    // send messages from one user to all the others    public static void SendMessage(string From, string Message)    {        StreamWriter swSenderSender;        e = new StatusChangedEventArgs(From + " says: " + Message);        OnStatusChanged(e); //show who says what         // create array of TCP clients, size = num of users we have        TcpClient[] tcpClients = new TcpClient[ChatServer.htUsers.Count];        ChatServer.htUsers.Values.CopyTo(tcpClients, 0); //copy object to array        for (int i = 0; i < tcpClients.Length; i++) //loop through client list        {            try //try sending a message to each            {                if (Message.Trim() == "" || tcpClients[i] == null)                {                    continue; //if blank or if connection is null, break out                }                // send message to the current user in the loop                swSenderSender = new StreamWriter(tcpClients[i].GetStream());                swSenderSender.WriteLine(From + " says: " + Message);                swSenderSender.Flush();                swSenderSender = null;            }            catch // if problem, user is gone, remove him            {                RemoveUser(tcpClients[i]);            }        }    }

SendMessage( ) Method

SendMessage( ) method sends a message from a specific chat client to all the others.

Page 28: Multi-Client/Server GUI Application.  Overview As part of the TCP

    public void StartListening()    {        IPAddress ipaLocal = ipAddress; //get IP of 1st device        tlsClient = new TcpListener(1986); //create listener object        tlsClient.Start(); //start listener and listen for connections        ServRunning = true; //check for true before checking for connections        thrListener = new Thread(KeepListening); //start new host thread        thrListener.Start();    }     private void KeepListening()    {        while (ServRunning == true) //while server is running...        {            tcpClient = tlsClient.AcceptTcpClient(); //accept pending conn            Connection newConnection = new Connection(tcpClient); //new conn        }    }}

StartListening( ) & KeepListening( ) Methods

StartListening( ) method is the one we called inside Form1, and it initiates all other events. It defines the first needed objects and starts a new thread that keeps listening for connections, which is the KeepListening( ) method.

Page 29: Multi-Client/Server GUI Application.  Overview As part of the TCP

The Connection Class

Looking inside KeepListening() method you will see that a new instance of an object of type Connection is created for each connected client (user). The Connection Class implements these objects.

class Connection //this class handles connections, an instance for each user{    TcpClient tcpClient;    private Thread thrSender; //thread to send info to the client    private StreamReader srReceiver;    private StreamWriter swSender;    private string currUser;    private string strResponse;     public Connection(TcpClient tcpCon) //constructor of the class    {        tcpClient = tcpCon;        thrSender = new Thread(AcceptClient); //thread that accepts client        thrSender.Start(); //thread to call the AcceptClient() method    }     private void CloseConnection()    {        tcpClient.Close(); //closes currently open objects        srReceiver.Close();        swSender.Close();    }

Page 30: Multi-Client/Server GUI Application.  Overview As part of the TCP

    private void AcceptClient() //occurs when new client is accepted    {        srReceiver = new System.IO.StreamReader(tcpClient.GetStream());        swSender = new System.IO.StreamWriter(tcpClient.GetStream());        currUser = srReceiver.ReadLine(); //read account info from client        if (currUser != "") //response from client!!!        {            if (ChatServer.htUsers.Contains(currUser) == true)            { // store user name in hash table                swSender.WriteLine("0|This username already exists.");                swSender.Flush(); // 0 means 'not connected'                CloseConnection();                return;            }            else if (currUser == "Administrator")            {                swSender.WriteLine("0|This username is reserved.");                swSender.Flush(); // 0 means 'not connected'                CloseConnection();                return;            }            else            {                swSender.WriteLine("1");                swSender.Flush(); // 1 means connected successfully                ChatServer.AddUser(tcpClient, currUser);            } //add user to has tables and start listening        }        else        {            CloseConnection();            return;        }

AcceptClient( ) Method

Page 31: Multi-Client/Server GUI Application.  Overview As part of the TCP

        try        {            // Keep waiting for a message from the user            while ((strResponse = srReceiver.ReadLine()) != "")            {                // If it's invalid, remove the user                if (strResponse == null)                {                    ChatServer.RemoveUser(tcpClient);                }                else                {                    // Otherwise send the message to all the other users                    ChatServer.SendMessage(currUser, strResponse);                }            }        }        catch        {            // If anything went wrong with this user, disconnect him            ChatServer.RemoveUser(tcpClient);        }    }}The the constructor initializes the TcpClient object. The CloseConnection( ) method is called when we want to get rid of a currently connected client The AcceptClient( ) method checks to make sure the username is valid and if all is fine, it adds the user to the hash tables. Otherwise it removes the user.

AcceptClient( ) Methodconcluded

Page 32: Multi-Client/Server GUI Application.  Overview As part of the TCP

References:http://www.geekpedia.com/tutorial239_Csharp-Chat-Part-1---Building-the-Chat-Client.htmlhttp://www.geekpedia.com/tutorial240_Csharp-Chat-Part-2---Building-the-Chat-Server.html

Page 33: Multi-Client/Server GUI Application.  Overview As part of the TCP
Page 34: Multi-Client/Server GUI Application.  Overview As part of the TCP
Page 35: Multi-Client/Server GUI Application.  Overview As part of the TCP
Page 36: Multi-Client/Server GUI Application.  Overview As part of the TCP