1
Feb
0

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

Pro Silverlight in C# 2
Cover

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

Enjoyed reading this post?
Subscribe to the RSS feed and have all new posts delivered straight to you.

Comments are closed.

Celadon theme by the Themes Boutique