Chat Server and WPF Client
Download the code on https://github.com/scotpatti/chatdemo 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());
}
}
}
}
}