A Silverlight Chat using socket and C#/.Net for server – Easy, fast and stable
In this post, I write about creating a Chat in silverlight (for the client) and .Net for the server.
The both are using socket to communicate, and the server is only doing a broadcast of the messages to others clients.
The post is BIG because I just past the code ^^
The Server
For the server open Visual Studio (or Visual C# express), create a new console application.
The main
The main creates a new server, starts the it and wait for the key enter to be pressed to stop, only few lines are needed as fallow :
static void Main(string[] args)
{
Server server = new Server();
server.Start();
/* wait for the enter key */
Console.ReadLine();
server.Stop();
Console.WriteLine(\"ChatBox shut down.\");
}
Then in the server class only has basic methods, when a clients is connected it receives last messages, if a new message is received it is send to every clients (include the author), if someone is disconnected the client is deleted from the list. Each messages and clients status are saved in a log file and printed in the console :
class Server
{
// used to send the last messages to a new client
protected static int MaxSavedMessages = 10;
// connexion port
protected static int Ports = 4532;
// current clients
protected List clients;
// messages
protected Queue messages;
// to store log of the tchat
protected TextWriter tw;
public Server()
{
clients = new List();
messages = new Queue();
tw = new StreamWriter("log.txt");
}
private TcpListener listener;
public void Start()
{
Console.WriteLine("[Start]");
isStopped = false;
// Create the listener.
listener = new TcpListener(IPAddress.Any, Ports);
// Begin listening. This method returns immediately.
listener.Start();
// Wait for a connection. This method returns immediately.
// The waiting happens on a separate thread.
listener.BeginAcceptTcpClient(OnAcceptTcpClient, null);
}
public void OnAcceptTcpClient(IAsyncResult ar)
{
if (isStopped) return;
Console.WriteLine("[Connexion]");
lock (tw)
{
tw.WriteLine("[Connexion]");
}
// Wait for the next connection.
listener.BeginAcceptTcpClient(OnAcceptTcpClient, null);
// Handle this connection.
try
{
TcpClient client = listener.EndAcceptTcpClient(ar);
SendMessages(client);
lock (clients)
{
clients.Add(client);
}
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
clientThread.Start(client);
}
catch (Exception err)
{
Console.WriteLine(err.Message);
}
}
private void SendMessages(TcpClient client){
NetworkStream clientStream = client.GetStream();
lock (messages)
{
foreach (byte[] bt in messages)
{
clientStream.Write(bt,0,bt.Length);
}
}
}
private void HandleClientComm(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
byte[] message = new byte[4096];
int bytesRead;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 4096);
}
catch
{
break;
}
if (bytesRead == 0)
{
break;
}
//message has successfully been received
ASCIIEncoding encoder = new ASCIIEncoding();
Console.WriteLine(encoder.GetString(message, 0, bytesRead));
lock (tw)
{
tw.WriteLine(encoder.GetString(message, 0, bytesRead));
}
broadcast(message, bytesRead);
}
lock (clients)
{
clients.Remove(tcpClient);
}
Console.WriteLine("[Deconnexion]");
lock (tw)
{
tw.WriteLine("[Deconnexion]");
}
tcpClient.Close();
}
protected void broadcast(byte [] bt, int lenght)
{
lock (clients)
{
foreach (TcpClient client in clients)
{
client.GetStream().Write(bt, 0, lenght);
}
}
byte [] mess = new byte[lenght];
Array.Copy(bt, 0, mess, 0, lenght);
lock (messages)
{
messages.Enqueue(mess);
if (messages.Count > MaxSavedMessages) messages.Dequeue();
}
}
private bool isStopped = false;
public void Stop()
{
isStopped = true;
try {
listener.Stop();
}
catch (Exception err) {
Console.WriteLine(err.Message);
}
lock (clients)
{
foreach (TcpClient client in clients) {
client.Close();
}
}
lock (tw) {
tw.Close();
}
}
}
The Client
The gui is composed of a list (to store message) a textbox (to write message) and two textedit to show information :
(Do not forget to give the size you want in the mainpage user control properties)
In the MainPage.cs, a random name is given to the client :
public partial class MainPage : UserControl {
protected Client client;
protected string Login = string.Format("guest-{0:000}",(new Random()).Next(999));
public MainPage()
{
InitializeComponent();
this.Loaded += new RoutedEventHandler(MainPage_Loaded);
}
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
Chat.Items.Add("Welcome to BoOga Chat V1.2");
Chat.Items.Add("");
LoginText.Text += Login;
client = new Client();
client.Connect();
CompositionTarget.Rendering += new EventHandler(CompositionTarget_Rendering);
KeyDown += new KeyEventHandler(MainPage_KeyDown);
}
void MainPage_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && InputForm.Text.Length != 0)
{
client.Send_Message(Login, InputForm.Text);
InputForm.Text = string.Empty;
}
}
void CompositionTarget_Rendering(object sender, EventArgs e)
{
if (client.PacketManager.IsPacketAvailable())
{
PacketManager.Message mess = client.PacketManager.DequeuePacket();
Chat.Items.Insert(Chat.Items.Count - 1, string.Format("[{2}] {0} : {1}", mess.login, mess.message, mess.date));
Chat.ScrollIntoView(Chat.Items[Chat.Items.Count - 1]);
}
Chat.SelectedIndex = Chat.Items.Count - 2;
}
public void Error(string title, string error)
{
Dispatcher.BeginInvoke(() =>
{
Chat.Items.Insert(Chat.Items.Count - 1, string.Format("[Infos] {0} -- {1}", title, error));
Chat.ScrollIntoView(Chat.Items[Chat.Items.Count - 1]);
Chat.SelectedIndex = Chat.Items.Count - 2;
});
//Chat.Items.Add(string.Format("[ERROR] {0} -- {1}", title, error));
}
}
Finally the last two important things are the client class and the packetmanager.
Let’s begin with the packetmanager, it is used to store message received from the server and store them in a queue, then the main page print the complete message.
public class PacketManager{
//Paquets pret a l'emploi sauf le dernier
private Queue packets = new Queue();
private StringBuilder buffer = new StringBuilder();
// constructeur
public PacketManager()
{}
// prendre un paquet
public Message DequeuePacket()
{
lock (this)
{
if (packets.Count != 0) return packets.Dequeue();
}
return new Message();
}
//un paquet est pret
public bool IsPacketAvailable()
{
//return the number of received data
lock (this)
{
return packets.Count != 0;
}
}
//ajout de donnée
public bool AddPacketData(byte[] in_data, int in_dataLength)
{
//if there is no data
if (in_dataLength == 0)
{
return false;
}
lock (this)
{
//convert data
buffer.Append(Encoding.UTF8.GetString(in_data, 0, in_dataLength));
string data = buffer.ToString();
int startIndex = 0;
int endIndex = 0;
try
{
while ((endIndex = data.IndexOf("#", startIndex)) != -1)
{
packets.Enqueue(new Message(data.Substring(startIndex, endIndex - startIndex)));
startIndex = endIndex + 1;
}
}
catch
{}
buffer.Length = 0;
if (data.Length > startIndex) buffer.Append(data.Substring(startIndex));
}
return IsPacketAvailable();
}
public class Message{
public string login = string.Empty;
public string message = string.Empty;
public string date = string.Empty;
public Message() {
}
public Message(string in_data)
{
try{
string[] splited = in_data.Split('$');
login = splited[0];
message = splited[1];
date = splited[2];
}
catch { }
}
}
}
And now lets finish with the client class :
public class Client{
Socket
protected Socket _clientSocket;
//buffer d'envoi
protected byte[] _sendBuffer;
// buffer de reception
protected byte[] _receiveBuffer;
//savoir si le client est connecté
protected bool _isConnected;
public bool IsConnected{
get { return _isConnected; }}
// gestionnaire de paquet
public PacketManager PacketManager;
// constructeur
public Client(){
_sendBuffer = new byte[1024];
_receiveBuffer = new byte[1024];
PacketManager = new PacketManager();
}
// connecté le client
public void Connect(){
try
{
//si il n'est pas deja connecté
if (!_isConnected){
DnsEndPoint endPoint = new DnsEndPoint(Application.Current.Host.Source.Host, 4533);
_clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.UserToken = _clientSocket;
args.RemoteEndPoint = endPoint;
args.Completed += new EventHandler(OnConnect);
//connection
_clientSocket.ConnectAsync(args);
} }
catch (Exception e) {
// Do What you want in case of error Error("Error Connect", e.Message);
}
}
//callback une fois connecté
protected void OnConnect(object sender, SocketAsyncEventArgs args){
try
{
// test de connection
_isConnected = (args.SocketError == SocketError.Success && _clientSocket.Connected);
//si on est connecté
if (_isConnected){ //on indique quoi faire pour recevoir
args.SetBuffer(_receiveBuffer, 0, _receiveBuffer.Length);
args.Completed -= new EventHandler(this.OnConnect);
args.Completed += new EventHandler(this.OnReceive);
//reception asynchrone
_clientSocket.ReceiveAsync(args);
// Do What you want in case of error Error("okay", "connected");
}
else
{
// Do What you want in case of error Error("You Cannot connect to host", args.SocketError.ToString());
}
}
catch (Exception e) {
// Do What you want in case of error Error("Error OnConnect", e.Message);
}
}
//en case de reception
protected void OnReceive(object sender, SocketAsyncEventArgs args){
try
{
if (args.SocketError == SocketError.Success && args.BytesTransferred != 0)
{
//on ajoute ce qui est recu dans le
//gestionnaire de paquet
PacketManager.AddPacketData(args.Buffer, args.BytesTransferred);
//on en veut encore
_clientSocket.ReceiveAsync(args);
}
else
{
//indication de nouveau élément
//mais en fait rien n'est présent
//deconnexion
Disconnect();
}
}
catch (Exception e) {
// Do What you want in case of error Error("Error OnReceive", e.Message);
}
}
//envoi de paquet
public void Send(string data){
if (data.Length != 0 && _isConnected)
{
SendPacket(_clientSocket, data );
}
}
public static void SendPacket(Socket client, byte[] data){
try
{
//envoi asynchrone
SocketAsyncEventArgs e = new SocketAsyncEventArgs();
e.SetBuffer(data, 0, data.Length);
client.SendAsync(e);
}
catch (Exception ex)
{
// Do What you want in case of error Error("Error Sending data", ex.Message);
}
}
//envoi de string
public static void SendPacket(Socket client, string data){
SendPacket(client, Encoding.UTF8.GetBytes(data));
}
//deconnexion
public void Disconnect()
{
try
{
if (_clientSocket != null)
{
lock (_clientSocket)
{
_clientSocket.Shutdown(SocketShutdown.Both);
_clientSocket.Close();
_clientSocket = null;
}
// Do What you want in case of error Error("Error", "You have been disconnected");
}
}
catch (Exception e)
{
// Do What you want in case of error Error("Error", e.Message);
}
_isConnected = false;
}
public void Send_Message(string login, string message)
{
message.Replace('$', '?');
Send(String.Format("{0}${1}${2}#", login, message, DateTime.Now.ToString("HH:mm")));
}
}
Copy and paste everything in your projects. But, you have to know that I migrate my blog from DotClear to WordPress, so … you may find some errors (syntax, bad char, bad link, etc.) Sorry.
References
Source
The server: http://berenger.eu/blog/wp-content/uploads/2011/12/ServerForSilverlight.zip
The client: http://berenger.eu/blog/wp-content/uploads/2011/12/ClientChat.zip
The security server: http://berenger.eu/blog/wp-content/uploads/2011/12/ServerSecurity.zip
Subscribe to the RSS feed and have all new posts delivered straight to you.

