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.

using System;
using System.Windows.Input;

namespace ChatClient
{
    class DelegateCommand : ICommand
    {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _execute;

        public event EventHandler CanExecuteChanged;

        public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute(parameter);
        }

        public void RaiseCanExecuteChanged()
        {
            if (CanExecuteChanged != null)
            {
                CanExecuteChanged(this, EventArgs.Empty);
            }
        }
    }
}

You can see this in the ViewModel used below:

using System.ComponentModel;
using ChatClient.Models;

namespace ChatClient.ViewModels
{
    /// <summary>
    /// Adaptive code only. You should only see things here that adapt
    /// the Model to the view. This is an abstraction of the Model for
    /// the express use by the View.
    /// </summary>
    class ClientViewModel : INotifyPropertyChanged
    {
        #region Properties
        //Elements bound to by the view
        public string Message {
            get { return _clientModel.CurrentMessage; }
            set
            {
                _clientModel.CurrentMessage = value;
                NotifyPropertyChanged("Message");
            }
        }

        public string MessageBoard
        {
            get { return _clientModel.MessageBoard; }
            set
            {
                _clientModel.MessageBoard = value;
                NotifyPropertyChanged("MessageBoard");
            }
        }

        public DelegateCommand ConnectCommand { get; set; }
        public DelegateCommand SendCommand { get; set; }
        #endregion

        #region Private and Internal Vars/Props
        private readonly ClientModel _clientModel;
        #endregion

        /// <summary>
        /// Constructor creates the Model!
        /// </summary>
        public ClientViewModel()
        {
            //Create ourselves a model
            _clientModel = new ClientModel();
            //Subscribe to the Model's PropertyChanged event
            _clientModel.PropertyChanged += ClientModelChanged;
            //Create our two Command objects
            ConnectCommand = new DelegateCommand(
                a => _clientModel.Connect(),
                b => !_clientModel.Connected
            );
            SendCommand = new DelegateCommand(
                a => _clientModel.Send(),
                b => _clientModel.Connected
            );
        }

        #region Event Listeners
        private void ClientModelChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName.Equals("Connected"))
            {
                NotifyPropertyChanged("Connected");
                ConnectCommand.RaiseCanExecuteChanged();
                SendCommand.RaiseCanExecuteChanged();
            }
            else if (e.PropertyName.Equals("MessageBoard"))
            {
                NotifyPropertyChanged("MessageBoard");
            }
        }
        #endregion

        #region NPC Implementation
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string prop)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
            }
        }
        #endregion NPC Implementation
    }
}

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

using System.ComponentModel;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ChatClient.Models
{
    class ClientModel : INotifyPropertyChanged
    {
        private TcpClient _socket;
        private NetworkStream _stream;

        private string _messageBoard;
        public string MessageBoard {
            get { return _messageBoard; }
            set
            {
                _messageBoard = value;
                NotifyPropertyChanged("MessageBoard");
            }
        }
        private string _currentMessage;
        public string CurrentMessage
        {
            get {return _currentMessage;}
            set
            {
                _currentMessage = value;
                NotifyPropertyChanged("CurrentMessage");
            }
        }
        private bool _connected;

        public ClientModel()
        {
            _connected = false;
        }

        public bool Connected {
            get {return _connected; }
            set {
                _connected = value;
                NotifyPropertyChanged("Connected");
            }
        }

        public void Connect()
        {
            _socket = new TcpClient();
            _socket.Connect("127.0.0.1", 8888);
            _stream = _socket.GetStream();
            Connected = true;
            Send();
            _messageBoard = "Welcome: " + _currentMessage;
            var thread = new Thread(GetMessage);
            thread.Start();
        }

        public void Send()
        {
            WriteString(_currentMessage + "$");
        }

        private void GetMessage()
        {
            while (true)
            {
                string msg = ReadString();
                MessageBoard += "\r\n" + msg;
            }
        }

        private string ReadString()
        {
            var bytes = new byte[16384];
            _stream.Read(bytes, 0, _socket.ReceiveBufferSize);
            string msg = Encoding.ASCII.GetString(bytes);
            int index = msg.IndexOf("$") > 0 ? msg.IndexOf("$")
                : msg.IndexOf('\0');
            return msg.Substring(0, index);

        }

        private void WriteString(string msg)
        {
            byte[] bytes = Encoding.ASCII.GetBytes(msg);
            _stream.Write(bytes, 0, bytes.Length);
            _stream.Flush();
        }


        #region NPC
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(string prop)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
            }
        }
        #endregion
    }
}

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

using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace ChatServer
{
    class Program
    {
        public static Hashtable ClientList = new Hashtable();

        static void Main()
        {
            var serverSocket = new TcpListener(IPAddress.Any, 8888);
            serverSocket.Start();
            Console.WriteLine("Chat server started...");
            while (true)
            {
                //This next line of code actually blocks
                TcpClient clientSocket = serverSocket.AcceptTcpClient();
                //Somebody connected and set us data
                string dataFromClient = GetStringFromStream(clientSocket);
                ClientList.Add(dataFromClient, clientSocket);
                Broadcast(dataFromClient + " joined.", dataFromClient, false);
                Console.WriteLine(dataFromClient + " joined cat room.");
                var client = new HandleClient();
                client.StartClient(clientSocket, dataFromClient);
            }
        }

        public static void Broadcast(string msg, string uname, bool flag)
        {
            foreach (DictionaryEntry item in ClientList)
            {
                var broadcastSocket = (TcpClient)item.Value;
                NetworkStream broadcastStream = broadcastSocket.GetStream();
                byte[] broadcastBytes = flag ? Encoding.ASCII.GetBytes(uname + " says: " + msg)
                    : Encoding.ASCII.GetBytes(msg);
                broadcastStream.Write(broadcastBytes, 0, broadcastBytes.Length);
                broadcastStream.Flush();
            }
        }

        public static string GetStringFromStream(TcpClient clientSocket)
        {
            var bytesFrom = new byte[16384];
            NetworkStream networkStream = clientSocket.GetStream();
            networkStream.Read(bytesFrom, 0, clientSocket.ReceiveBufferSize);
            string dataFromClient = Encoding.ASCII.GetString(bytesFrom);
            return dataFromClient.Substring(0, dataFromClient.IndexOf("$", StringComparison.Ordinal));
        }
    }
}

The second file is the part handling each client.

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

namespace ChatServer
{
    public class HandleClient
    {
        private TcpClient _clientSocket;
        private string _clientNumber;

        public void StartClient(TcpClient clientSocket, string clientNumber)
        {
            _clientNumber = clientNumber;
            _clientSocket = clientSocket;
            var thread = new Thread(DoChat);
            thread.Start();
        }

        private void DoChat()
        {
            var bytesFrom = new byte[16384];
            int requestCount = 0;
            while (true)
            {
                try
                {
                    requestCount += 1;
                    string dataFromClient = Program.GetStringFromStream(_clientSocket);
                    Console.WriteLine("From Client - " + _clientNumber + ": " + dataFromClient);
                    Program.Broadcast(dataFromClient, _clientNumber, true);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
            }
        }
    }
}

PrinciplesOfNetworkingCourse/Programs/ChatExample (last edited 2016-09-06 23:10:53 by 71-88-174-29)