Browse DevX
Sign up for e-mail newsletters from DevX


Home-brew Your Own Instant Messenger App with Visual Studio .NET : Page 2

Learn .NET network programming by writing your own chat application, and in the process, create a slick client-server application that supports simultaneous conversations with multiple clients.




Building the Right Environment to Support AI, Machine Learning and Deep Learning

Building the Server
There are two components in our chat application: server and client. I'll start by building the server. For the server, I will create a console application project using Visual Studio 2005 beta 2 (you can also use Visual Studio .NET 2003). Name the project 'Server.'

In the default Module1.vb, I'll first import the System.Net.Sockets namespace; it contains all the relevant classes for my chat application.

Imports System.Net.Sockets

Next, I'll declare a constant containing the port number to use for this application; I've selected port number 500. If you have a firewall installed on the server (or client), be sure to open up port 500 for this application to work.

Const portNo As Integer = 500

I also need to define the local address to listen to and then create an instance of the TcpListen() class to use for listening for connections from TCP clients:

Dim localAdd As System.Net.IPAddress = _ System.Net.IPAddress.Parse("") Dim listener As New TcpListener(localAdd, portNo)

In the Main() function, I use the Start() method from the TcpListener class to start listening for incoming connection requests. The AcceptTcpClient() method is a blocking call and execution will not continue until a connection is established. As my server needs to service multiple clients at the same time, I will create an instance of the ChatClient (which I will define shortly) for each user. The server will loop indefinitely, accepting clients as they connect:

Sub Main() listener.Start() While True Dim user As New _ ChatClient(listener.AcceptTcpClient) End While End Sub

The source for Module1.vb looks like this:

Imports System.Net.Sockets Module Module1 Const portNo As Integer = 500 Dim localAdd As System.Net.IPAddress = _ System.Net.IPAddress.Parse("") Dim listener As New _ System.Net.Sockets.TcpListener(localAdd, portNo) Sub Main() listener.Start() While True Dim user As New _ ChatClient(listener.AcceptTcpClient) End While End Sub End Module

The next step is to define the ChatClient class. The ChatClient class is used to represent information of each client connecting to the server. Add a new Class to your project in Visual Studio 2005 and name it ChatClient.vb. As usual, the first thing to do is to import the System.Net.Sockets namespace:

Imports System.Net.Sockets

In the ChatClient class, first define the various private members (their uses are described in the comments in the code). You also declare a HashTable object (AllClients) to store a list of all clients connecting to the server. The reason for declaring it as a shared member is to ensure all instances of the ChatClient class are able to obtain a list of all the clients currently connected to the server:

Public Class ChatClient '---contains a list of all the clients Public Shared AllClients As New HashTable '---information about the client Private _client As TcpClient Private _clientIP As String Private _ClientNick As String '---used for sending/receiving data Private data() As Byte '---is the nick name being sent? Private ReceiveNick As Boolean = True

When a client gets connected to the server, the server will create an instance of the ChatClient class and then pass the TcpClient variable (client) to the constructor of the class. You will also get the IP address of the client and use it as an index to identify the client in the HashTable object. The BeginRead() method will begin an asynchronous read from the NetworkStream object (_client.GetStream) in a separate thread. This allows the server to remain responsive and continue accepting new connections from other clients. When the reading is complete, control will be transferred to the ReceiveMessage() function (which I will define shortly).

Public Sub New(ByVal client As TcpClient) _client = client '---get the client IP address _clientIP = client.Client.RemoteEndPoint.ToString '---add the current client to the hash table AllClients.Add(_clientIP, Me) '---start reading data from the client in a ' separate thread ReDim data(_client.ReceiveBufferSize) _client.GetStream.BeginRead(data, 0, _ CInt(_client.ReceiveBufferSize), _ AddressOf ReceiveMessage, Nothing) End Sub

In the ReceiveMessage() function, I first call the EndRead() method to handle the end of an asynchronous read. Here, I check if the number of bytes read is less then 1. If it is, the client has disconnected and you need to remove the client from the HashTable object (using the IP address of the client as an index into the hash table). I also want to broadcast a message to all the clients telling them that this particular client has left the chat. I do this using the Broadcast() function (again, I will define this shortly).

For simplicity, assume that the client will send the nickname of the user the first time it connects to the server. Subsequently, you will just broadcast whatever was sent by the client to everyone. Once this is done, the server will proceed to perform the asynchronous read from the client again.

Public Sub ReceiveMessage(ByVal ar As IAsyncResult) '---read from client--- Dim bytesRead As Integer Try SyncLock _client.GetStream bytesRead = _client.GetStream.EndRead(ar) End SyncLock '---client has disconnected If bytesRead < 1 Then AllClients.Remove(_clientIP) Broadcast(_ClientNick & _ " has left the chat.") Exit Sub Else '---get the message sent Dim messageReceived As String = _ System.Text.Encoding.ASCII. _ GetString(data, 0, bytesRead) '---client is sending its nickname If ReceiveNick Then _ClientNick = messageReceived '---tell everyone client has entered ' the chat Broadcast(_ClientNick & _ " has joined the chat.") ReceiveNick = False Else '---broadcast the message to everyone Broadcast(_ClientNick & ">" & _ messageReceived) End If End If '---continue reading from client SyncLock _client.GetStream _client.GetStream.BeginRead(data, 0, _ CInt(_client.ReceiveBufferSize), _ AddressOf ReceiveMessage, Nothing) End SyncLock Catch ex As Exception AllClients.Remove(_clientIP) Broadcast(_ClientNick & _ " has left the chat.") End Try End Sub

One thing to note in the above code is that you need to use the SyncLock statement to prevent multiple threads from using the NetworkStream object. Without the SyncLock statement your application will be unpredictable, perhaps being subject to frequent crashes. This scenario is likely to occur when your server is connected to multiple clients and all of them are trying to access the NetworkStream object at the same time.

The SendMessage() function allows the server to send a message to the client.

Public Sub SendMessage(ByVal message As String) Try '---send the text Dim ns As System.Net.Sockets.NetworkStream SyncLock _client.GetStream ns = _client.GetStream End SyncLock Dim bytesToSend As Byte() = _ System.Text.Encoding.ASCII.GetBytes(message) ns.Write(bytesToSend, 0, bytesToSend.Length) ns.Flush() Catch ex As Exception Console.WriteLine(ex.ToString) End Try End Sub

Finally, the Broadcast() function sends a message to all the clients stored in the AllClients HashTable object.

Public Sub Broadcast(ByVal message As String) '---log it locally Console.WriteLine(message) Dim c As DictionaryEntry For Each c In AllClients '---broadcast message to all users CType(c.Value, _ ChatClient).SendMessage(message & vbCrLf) Next End Sub

Thanks for your registration, follow us on our social networks to keep up-to-date