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 }