|
Size: 1110
Comment:
|
← Revision 13 as of 2016-09-06 23:10:53 ⇥
Size: 12167
Comment:
|
| Deletions are marked like this. | Additions are marked like this. |
| Line 3: | Line 3: |
| == Client == For the client code I used WPF and the MVVM pattern. From the bottom up the first thing I used is a DelegateCommand |
Download the code on [[https://github.com/scotpatti/chatdemo|github]] or an older version from here: [[attachment: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. |
| Line 45: | Line 46: |
}}} |
}}} You can see this in the ViewModel used below: {{{#!csharp 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. {{{#!csharp 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 {{{#!csharp 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. {{{#!csharp 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()); } } } } } }}} |
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());
}
}
}
}
}