Chat Server and WPF Client

Download the code on github or an older version from here: ChatServer2013.zip

Chat Client

For the client code I used WPF and the MVVM pattern. From the bottom up the first thing I used is a DelegateCommand for the ICommands needed for buttons and what not. This makes the use of ICommand almost easy.

   1 using System;
   2 using System.Windows.Input;
   3 
   4 namespace ChatClient
   5 {
   6     class DelegateCommand : ICommand
   7     {
   8         private readonly Predicate<object> _canExecute;
   9         private readonly Action<object> _execute;
  10 
  11         public event EventHandler CanExecuteChanged;
  12 
  13         public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
  14         {
  15             _execute = execute;
  16             _canExecute = canExecute;
  17         }
  18 
  19         public bool CanExecute(object parameter)
  20         {
  21             return _canExecute == null || _canExecute(parameter);
  22         }
  23 
  24         public void Execute(object parameter)
  25         {
  26             _execute(parameter);
  27         }
  28 
  29         public void RaiseCanExecuteChanged()
  30         {
  31             if (CanExecuteChanged != null)
  32             {
  33                 CanExecuteChanged(this, EventArgs.Empty);
  34             }
  35         }
  36     }
  37 }

You can see this in the ViewModel used below:

   1 using System.ComponentModel;
   2 using ChatClient.Models;
   3 
   4 namespace ChatClient.ViewModels
   5 {
   6     /// <summary>
   7     /// Adaptive code only. You should only see things here that adapt
   8     /// the Model to the view. This is an abstraction of the Model for
   9     /// the express use by the View.
  10     /// </summary>
  11     class ClientViewModel : INotifyPropertyChanged
  12     {
  13         #region Properties
  14         //Elements bound to by the view
  15         public string Message {
  16             get { return _clientModel.CurrentMessage; }
  17             set
  18             {
  19                 _clientModel.CurrentMessage = value;
  20                 NotifyPropertyChanged("Message");
  21             }
  22         }
  23 
  24         public string MessageBoard
  25         {
  26             get { return _clientModel.MessageBoard; }
  27             set
  28             {
  29                 _clientModel.MessageBoard = value;
  30                 NotifyPropertyChanged("MessageBoard");
  31             }
  32         }
  33 
  34         public DelegateCommand ConnectCommand { get; set; }
  35         public DelegateCommand SendCommand { get; set; }
  36         #endregion
  37 
  38         #region Private and Internal Vars/Props
  39         private readonly ClientModel _clientModel;
  40         #endregion
  41 
  42         /// <summary>
  43         /// Constructor creates the Model!
  44         /// </summary>
  45         public ClientViewModel()
  46         {
  47             //Create ourselves a model
  48             _clientModel = new ClientModel();
  49             //Subscribe to the Model's PropertyChanged event
  50             _clientModel.PropertyChanged += ClientModelChanged;
  51             //Create our two Command objects
  52             ConnectCommand = new DelegateCommand(
  53                 a => _clientModel.Connect(),
  54                 b => !_clientModel.Connected
  55             );
  56             SendCommand = new DelegateCommand(
  57                 a => _clientModel.Send(),
  58                 b => _clientModel.Connected
  59             );
  60         }
  61 
  62         #region Event Listeners
  63         private void ClientModelChanged(object sender, PropertyChangedEventArgs e)
  64         {
  65             if (e.PropertyName.Equals("Connected"))
  66             {
  67                 NotifyPropertyChanged("Connected");
  68                 ConnectCommand.RaiseCanExecuteChanged();
  69                 SendCommand.RaiseCanExecuteChanged();
  70             }
  71             else if (e.PropertyName.Equals("MessageBoard"))
  72             {
  73                 NotifyPropertyChanged("MessageBoard");
  74             }
  75         }
  76         #endregion
  77 
  78         #region NPC Implementation
  79         public event PropertyChangedEventHandler PropertyChanged;
  80 
  81         private void NotifyPropertyChanged(string prop)
  82         {
  83             if (PropertyChanged != null)
  84             {
  85                 PropertyChanged(this, new PropertyChangedEventArgs(prop));
  86             }
  87         }
  88         #endregion NPC Implementation
  89     }
  90 }

The ViewModel above creates the Model in the constructor. The Model is shown below.

   1 using System.ComponentModel;
   2 using System.Net.Sockets;
   3 using System.Text;
   4 using System.Threading;
   5 
   6 namespace ChatClient.Models
   7 {
   8     class ClientModel : INotifyPropertyChanged
   9     {
  10         private TcpClient _socket;
  11         private NetworkStream _stream;
  12 
  13         private string _messageBoard;
  14         public string MessageBoard {
  15             get { return _messageBoard; }
  16             set
  17             {
  18                 _messageBoard = value;
  19                 NotifyPropertyChanged("MessageBoard");
  20             }
  21         }
  22         private string _currentMessage;
  23         public string CurrentMessage
  24         {
  25             get {return _currentMessage;}
  26             set
  27             {
  28                 _currentMessage = value;
  29                 NotifyPropertyChanged("CurrentMessage");
  30             }
  31         }
  32         private bool _connected;
  33 
  34         public ClientModel()
  35         {
  36             _connected = false;
  37         }
  38 
  39         public bool Connected {
  40             get {return _connected; }
  41             set {
  42                 _connected = value;
  43                 NotifyPropertyChanged("Connected");
  44             }
  45         }
  46 
  47         public void Connect()
  48         {
  49             _socket = new TcpClient();
  50             _socket.Connect("127.0.0.1", 8888);
  51             _stream = _socket.GetStream();
  52             Connected = true;
  53             Send();
  54             _messageBoard = "Welcome: " + _currentMessage;
  55             var thread = new Thread(GetMessage);
  56             thread.Start();
  57         }
  58 
  59         public void Send()
  60         {
  61             WriteString(_currentMessage + "$");
  62         }
  63 
  64         private void GetMessage()
  65         {
  66             while (true)
  67             {
  68                 string msg = ReadString();
  69                 MessageBoard += "\r\n" + msg;
  70             }
  71         }
  72 
  73         private string ReadString()
  74         {
  75             var bytes = new byte[16384];
  76             _stream.Read(bytes, 0, _socket.ReceiveBufferSize);
  77             string msg = Encoding.ASCII.GetString(bytes);
  78             int index = msg.IndexOf("$") > 0 ? msg.IndexOf("$")
  79                 : msg.IndexOf('\0');
  80             return msg.Substring(0, index);
  81 
  82         }
  83 
  84         private void WriteString(string msg)
  85         {
  86             byte[] bytes = Encoding.ASCII.GetBytes(msg);
  87             _stream.Write(bytes, 0, bytes.Length);
  88             _stream.Flush();
  89         }
  90 
  91 
  92         #region NPC
  93         public event PropertyChangedEventHandler PropertyChanged;
  94 
  95         private void NotifyPropertyChanged(string prop)
  96         {
  97             if (PropertyChanged != null)
  98             {
  99                 PropertyChanged(this, new PropertyChangedEventArgs(prop));
 100             }
 101         }
 102         #endregion
 103     }
 104 }

Finally the XAML View for this project was very simple and is bound (e.g. it creates its controller) to the ViewModel which is started from App.xaml in the StartupUri="MainWindow.xaml" ). Here is the view:

<Window x:Class="ChatClient.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:ChatClient.ViewModels"
        Title="Chat Client" Height="392" Width="561">
    <Window.DataContext>
        <my:ClientViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
        </Grid.RowDefinitions>
        <Label Content="Message:" HorizontalAlignment="Left" Margin="10,0,0,57" Width="58" Height="26" VerticalAlignment="Bottom"/>
        <TextBox Height="70" Margin="73,0,10,8" TextWrapping="Wrap" Text="{Binding Message}" VerticalAlignment="Bottom"/>
        <TextBox Margin="10,10,10,83" TextWrapping="Wrap" Text="{Binding MessageBoard}" ScrollViewer.CanContentScroll="True" VerticalScrollBarVisibility="Auto"/>
        <Button Content="Send" Command="{Binding SendCommand}" HorizontalAlignment="Left" Margin="10,0,0,35" VerticalAlignment="Bottom" Width="58" Height="22"/>
        <Button Content="Connect" Command="{Binding ConnectCommand}" HorizontalAlignment="Left" Margin="10,0,0,8" VerticalAlignment="Bottom" Width="58"/>
    </Grid>
</Window>

Chat Server

The server is in two files. The first is program.cs

   1 using System;
   2 using System.Collections;
   3 using System.Net;
   4 using System.Net.Sockets;
   5 using System.Text;
   6 
   7 namespace ChatServer
   8 {
   9     class Program
  10     {
  11         public static Hashtable ClientList = new Hashtable();
  12 
  13         static void Main()
  14         {
  15             var serverSocket = new TcpListener(IPAddress.Any, 8888);
  16             serverSocket.Start();
  17             Console.WriteLine("Chat server started...");
  18             while (true)
  19             {
  20                 //This next line of code actually blocks
  21                 TcpClient clientSocket = serverSocket.AcceptTcpClient();
  22                 //Somebody connected and set us data
  23                 string dataFromClient = GetStringFromStream(clientSocket);
  24                 ClientList.Add(dataFromClient, clientSocket);
  25                 Broadcast(dataFromClient + " joined.", dataFromClient, false);
  26                 Console.WriteLine(dataFromClient + " joined cat room.");
  27                 var client = new HandleClient();
  28                 client.StartClient(clientSocket, dataFromClient);
  29             }
  30         }
  31 
  32         public static void Broadcast(string msg, string uname, bool flag)
  33         {
  34             foreach (DictionaryEntry item in ClientList)
  35             {
  36                 var broadcastSocket = (TcpClient)item.Value;
  37                 NetworkStream broadcastStream = broadcastSocket.GetStream();
  38                 byte[] broadcastBytes = flag ? Encoding.ASCII.GetBytes(uname + " says: " + msg)
  39                     : Encoding.ASCII.GetBytes(msg);
  40                 broadcastStream.Write(broadcastBytes, 0, broadcastBytes.Length);
  41                 broadcastStream.Flush();
  42             }
  43         }
  44 
  45         public static string GetStringFromStream(TcpClient clientSocket)
  46         {
  47             var bytesFrom = new byte[16384];
  48             NetworkStream networkStream = clientSocket.GetStream();
  49             networkStream.Read(bytesFrom, 0, clientSocket.ReceiveBufferSize);
  50             string dataFromClient = Encoding.ASCII.GetString(bytesFrom);
  51             return dataFromClient.Substring(0, dataFromClient.IndexOf("$", StringComparison.Ordinal));
  52         }
  53     }
  54 }

The second file is the part handling each client.

   1 using System;
   2 using System.Net.Sockets;
   3 using System.Threading;
   4 
   5 namespace ChatServer
   6 {
   7     public class HandleClient
   8     {
   9         private TcpClient _clientSocket;
  10         private string _clientNumber;
  11 
  12         public void StartClient(TcpClient clientSocket, string clientNumber)
  13         {
  14             _clientNumber = clientNumber;
  15             _clientSocket = clientSocket;
  16             var thread = new Thread(DoChat);
  17             thread.Start();
  18         }
  19 
  20         private void DoChat()
  21         {
  22             var bytesFrom = new byte[16384];
  23             int requestCount = 0;
  24             while (true)
  25             {
  26                 try
  27                 {
  28                     requestCount += 1;
  29                     string dataFromClient = Program.GetStringFromStream(_clientSocket);
  30                     Console.WriteLine("From Client - " + _clientNumber + ": " + dataFromClient);
  31                     Program.Broadcast(dataFromClient, _clientNumber, true);
  32                 }
  33                 catch (Exception ex)
  34                 {
  35                     Console.WriteLine(ex.ToString());
  36                 }
  37             }
  38         }
  39     }
  40 }

PrinciplesOfNetworkingCourse/Programs/ChatExample (last edited 2016-09-06 23:10:53 by scot)