dcsimg
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

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.


advertisement
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("127.0.0.1")
    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("127.0.0.1")
    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