Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Javascript

Chat App with Web Sockets

5.00/5 (3 votes)
1 Aug 2022CPOL1 min read 9K   587  
Attempt to create a smallest chat application using Web Sockets
In this article, I have attempted to create the smallest chat application using web sockets using the least amount of code.

Introduction

This application is my attempt to create a smallest chat application using Web Sockets. I am trying to use the least amount of code to show how to use Web Sockets. Web Sockets is the technology that lets you push the data from the web server to web browser.

Image 1

Image 2

Background

To use Web Sockets, your web server needs to have ASP.NET 4.5 or higher and WebSocket Protocol installed under Add Roles and Features > Server Roles > Web Server > Web Server > Application Development > WebSocket Protocol.

Image 3

Using the Code

To setup the allocation, unzip WebSocket.zip to a C:\inetpub\wwwroot\WebSocket on your web server. Open IIS Console and create virtual directory called WebSocket.

Image 4

Next, point your browser to http://localhost/WebSocket/Chat.aspx.

The web allocation has two files: Handler1.ashx and Chat.aspx. Handler1.ashx handler file handles WebSockets request. It handles web socket request with HandleSocketRequest function. The Async function loops if the socket connection is opened. It uses Await to wait for the message and then broadcasts the massage to all the registered users.

VB.NET
Imports System.Web
Imports System.Web.Services
Imports System.Net.WebSockets
Imports System.Web.WebSockets
Imports System.Threading.Tasks

Public Class Handler1
    Implements System.Web.IHttpHandler

    Private oSockets As New Hashtable

    Sub ProcessRequest(ByVal context As HttpContext) _
                       Implements IHttpHandler.ProcessRequest
        If context.IsWebSocketRequest Then

            If context.Application("Sockets") IsNot Nothing Then
                oSockets = context.Application("Sockets")

                Dim sUserId As String = context.Request.QueryString("user")
                If oSockets.ContainsKey(sUserId) Then
                    context.Response.StatusCode = 500
                    context.Response.StatusDescription = "User " & _
                                     sUserId & " already logged in"
                    context.Response.End()
                End If

            End If

            Try
                context.AcceptWebSocketRequest(AddressOf HandleSocketRequest)
            Catch ex As Exception
                context.Response.StatusCode = 500
                context.Response.StatusDescription = ex.Message
                context.Response.End()
            End Try

        End If
    End Sub

    Async Function HandleSocketRequest_
        (context As System.Web.WebSockets.AspNetWebSocketContext) As Task
        Dim sUserId As String = context.QueryString("user")
        Dim oSocket As System.Net.WebSockets.WebSocket = context.WebSocket

        'Register user
        oSockets(sUserId) = oSocket
        context.Application("Sockets") = oSockets

        'Send RefreshUsers Msg to all users
        Dim oRefreshMsgBuffer As New ArraySegment(Of Byte)_
                              (Encoding.UTF8.GetBytes("{{RefreshUsers}}"))
        For Each oKey As DictionaryEntry In oSockets
            Dim oSocket2 As System.Net.WebSockets.WebSocket = oKey.Value
            Await oSocket2.SendAsync(oRefreshMsgBuffer, _
                  WebSocketMessageType.Text, True, Threading.CancellationToken.None)
        Next

        Const iMaxBufferSize As Integer = 64 * 1024
        Dim buffer = New Byte(iMaxBufferSize - 1) {}

        While oSocket.State = WebSocketState.Open 'Loop if Socket is open
            'Get Msg
            Dim result = Await oSocket.ReceiveAsync(New ArraySegment(Of Byte)(buffer),_
                         Threading.CancellationToken.None)
            Dim oBytes As Byte() = New Byte(result.Count - 1) {}
            Array.Copy(buffer, oBytes, result.Count)
            Dim oFinalBuffer As List(Of Byte) = New List(Of Byte)()
            oFinalBuffer.AddRange(oBytes)

            'Get Remaining Msg
            While result.EndOfMessage = False
                result = Await oSocket.ReceiveAsync_
                (New ArraySegment(Of Byte)(buffer), Threading.CancellationToken.None)
                oBytes = New Byte(result.Count - 1) {}
                Array.Copy(buffer, oBytes, result.Count)
                oFinalBuffer.AddRange(oBytes)
            End While

            If result.MessageType = WebSocketMessageType.Text Then
                Dim sMsg As String = Encoding.UTF8.GetString(oFinalBuffer.ToArray())

                'Send Msg to all users (including self)
                sMsg = sUserId & ": " & sMsg
                Dim oMsgBuffer As New ArraySegment(Of Byte)_
                               (Encoding.UTF8.GetBytes(sMsg))
                For Each oKey As DictionaryEntry In oSockets
                    Dim oDestSocket As System.Net.WebSockets.WebSocket = oKey.Value
                    Await oDestSocket.SendAsync(oMsgBuffer, _
                    WebSocketMessageType.Text, True, Threading.CancellationToken.None)
                Next
            End If
        End While

        'Send RefreshUsers Msg to all users
        For Each oKey As DictionaryEntry In oSockets
            Dim oSocket2 As System.Net.WebSockets.WebSocket = oKey.Value
            Await oSocket2.SendAsync(oRefreshMsgBuffer, _
            WebSocketMessageType.Text, True, Threading.CancellationToken.None)
        Next

        'Close Socket
        Await oSocket.CloseAsync(WebSocketCloseStatus.Empty, "", _
                                 Threading.CancellationToken.None)

        'Remove Socket from the List
        If oSockets.ContainsKey(sUserId) Then
            oSockets.Remove(sUserId)
            context.Application("Sockets") = oSockets
        End If

    End Function

    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

End Class

Chat.aspx file lets you login and broadcast message to all connected users. It is an ASP.NET web form that get the list of active users and uses JavaScript to send massage to Handler1.ashx.

VBScript
<%@ Page Language="VB"%>
<% 
    Dim sUserList As String = ""
    Dim sUserList2 As String = ""

    If Application("Sockets") IsNot Nothing Then
        For Each oSocket As DictionaryEntry In Application("Sockets")
            Dim sUser As String = oSocket.Key
            sUserList += "<option value=""" & sUser & """>" & _
                           sUser & "</option>" & vbCrLf

            If sUserList2 <> "" Then sUserList2 += vbCrLf
            sUserList2 += sUser
        Next
    End If

    If Request.QueryString("getUsers") = "1" Then
        Response.Write(sUserList)
        Response.End()

    ElseIf Request.QueryString("getUsers2") = "1" Then
        Response.Write(sUserList2)
        Response.End()

    ElseIf Request.QueryString("resetUsers") = "1" Then

        If Application("Sockets") IsNot Nothing Then
            For Each oEntry As DictionaryEntry In Application("Sockets")
                Dim oSocket As Object = oEntry.Value

                Try
                    oSocket.CloseOutputAsync_
                    (System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "", _
                     System.Threading.CancellationToken.None)
                    oSocket.CloseAsync_
                    (System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "", _
                     System.Threading.CancellationToken.None)
                Catch ex As Exception
                    'System.Threading.Thread.Sleep(1000)
                End Try

            Next

            Application("Sockets") = Nothing
        End If

        Response.Write("Users reseted")
        Response.End()

    End If
%>
<!DOCTYPE html>
<html>
    <head>
        <title>Chat App</title>
    </head>
    <body>
        <script>
            var websocket = null;

            function Open() {

                var sProtocol = window.location.protocol == "https:" ? "wss" : "ws";
                var uri = sProtocol + '://' + window.location.hostname + _
                          "/WebSocket/Handler1.ashx?user=" + escape(txtUser.value);
                websocket = new WebSocket(uri);           

                websocket.onopen = function () {
                    //Connected   
                    btnSend.disabled = false; 
                    btnClose.disabled = false; 
                    btnOpen.disabled = true; 
                    spStatus.style.color = "green";
                    RefreshUsers();
                };

                websocket.onclose = function () {
                    if (document.readyState == "complete") {
                        //Connection lost
                        btnSend.disabled = true;
                        btnClose.disabled = true; 
                        btnOpen.disabled = false; 
                        spStatus.style.color = "red";
                        selOtherUsers.length = 0;
                        RefreshUsers();
                    }
                };

                websocket.onmessage = function (event) {
                    if (event.data == "{{RefreshUsers}}") {
                        RefreshUsers();
                        return;
                    }

                    if (txtOutput.value != "") txtOutput.value += "\n";
                    txtOutput.value += event.data;
                };   

                websocket.onerror = function (event) {
                    alert('Could not connect.  Please try another name.');
                };   

                setTimeout(function () { RefreshUsers() }, 1000);
            }

            function Send() {
                if (txtMsg.value == "") return;
                websocket.send(txtMsg.value);
                txtMsg.value = "";
            }

            function Close() {
                websocket.close();
            }

            function RefreshUsers() {
                var oHttp = new XMLHttpRequest();
                oHttp.open("POST", "?getUsers=1", false);
                oHttp.setRequestHeader("Content-Type", _
                      "application/x-www-form-urlencoded");
                oHttp.onreadystatechange = function () _
                      { // Call a function when the state changes.
                    if (this.readyState === XMLHttpRequest.DONE && _
                        this.status === 200) {
                        selOtherUsers.innerHTML = oHttp.responseText;
                    }
                }
                oHttp.send();
            }
            
        </script>

        <div>
            <label for="txtUser">User Name</label>
            <input id="txtUser" value="Jack" />

            <button type="button" onclick="Open()" id="btnOpen">Login</button>
            <button type="button" onclick="Close()" 
                    id="btnClose" disabled>Log off</button>

            <span id="spStatus" style="color:red">⬤</span>

            <div style="float: right">
                <label for="selOtherUsers">Users</label>

                <button type="button" onclick="RefreshUsers()">&#x21bb;</button>
            </div>
        </div>

        <div>
            <table style="width: 100%; height: 300px">
                <tr>
                    <td style="width: 78%; height: 100%">
                        <textarea id="txtOutput" rows="1" 
                         style="margin-top: 10px; width: 100%; 
                         height: 100%" placeholder="Output"></textarea>
                    </td>
                    <td style="width: 20%; height: 100%; padding-left: 10px">
                        <select id="selOtherUsers" style="width: 100%; 
                         height: 100%" multiple>
                            <%=sUserList%>
                        </select>
                    </td>
                </tr>
            </table>
            
        </div>        

        <div>
            <textarea id="txtMsg" rows="5" wrap="soft" 
             style="width: 98%; margin-left: 3px; margin-top: 6px" 
             placeholder="Input Text"></textarea>
        </div>   

        <button type="button" onclick="Send()" id="btnSend" disabled>Send</button>  
    </body>
</html>

As a bonus, I created a Windows app that connects to Handler1.ashx and acts in pretty much the same way as Chat.aspx except for using VB.NET instead of JavaScript.

Image 5

It opens a WebSocket connection and waits for the massage to arrive.

VB.NET
Imports System.ComponentModel
Imports System.Net.WebSockets

Public Class Form1
    Dim oSocket As System.Net.WebSockets.ClientWebSocket = Nothing

    Private Async Sub btnOpen_Click(sender As Object, e As EventArgs) _
                                    Handles btnOpen.Click

        System.Net.ServicePointManager.SecurityProtocol =
            System.Net.SecurityProtocolType.Ssl3 Or
            System.Net.SecurityProtocolType.Tls12 Or
            System.Net.SecurityProtocolType.Tls11 Or
            System.Net.SecurityProtocolType.Tls

        Dim sServer As String = txtURL.Text 'localhost/WebSocket
        If sServer = "" Then
            MsgBox("Server URL is missing")
            Exit Sub
        End If

        Dim sProtocol As String = "ws"
        If chkSSL.Checked Then
            sProtocol = "wss"
        End If

        Dim sUrl As String = sProtocol & "://" & sServer & _
        "/Handler1.ashx?user=" & System.Web.HttpUtility.UrlEncode(txtUser.Text)

        oSocket = New System.Net.WebSockets.ClientWebSocket

        Try
            Await oSocket.ConnectAsync(New Uri(sUrl), Nothing)
        Catch ex As Exception
            MsgBox("Could not connect.  Please try another name. " & _
                                        ex.Message & ", URL: " & sUrl)
            Exit Sub
        End Try

        btnOpen.Enabled = False
        btnSend.Enabled = True
        btnClose.Enabled = True
        lbStatus.ForeColor = Color.FromArgb(0, 255, 0)

        Const iMaxBufferSize As Integer = 64 * 1024
        Dim buffer = New Byte(iMaxBufferSize - 1) {}

        While oSocket.State = WebSocketState.Open
            'Get Msg
            Dim result = Await oSocket.ReceiveAsync_
             (New ArraySegment(Of Byte)(buffer), Threading.CancellationToken.None)
            If result.MessageType = WebSocketMessageType.Text Then
                Dim oBytes As Byte() = New Byte(result.Count - 1) {}
                Array.Copy(buffer, oBytes, result.Count)
                Dim oFinalBuffer As List(Of Byte) = New List(Of Byte)()
                oFinalBuffer.AddRange(oBytes)

                'Get Remaining Msg
                While result.EndOfMessage = False
                    result = Await oSocket.ReceiveAsync_
                    (New ArraySegment(Of Byte)(buffer), _
                     Threading.CancellationToken.None)
                    oBytes = New Byte(result.Count - 1) {}
                    Array.Copy(buffer, oBytes, result.Count)
                    oFinalBuffer.AddRange(oBytes)
                End While

                Dim sMsg As String = System.Text.Encoding.UTF8.GetString_
                                     (oFinalBuffer.ToArray())

                If sMsg = "{{RefreshUsers}}" Then
                    RefreshUsers()
                Else
                    If txtOutput.Text <> "" Then txtOutput.Text += vbCrLf
                    txtOutput.Text += sMsg
                End If
            End If
        End While

        LogOff()
    End Sub

    Private Async Sub btnSend_Click(sender As Object, e As EventArgs) _
                                    Handles btnSend.Click
        If IsNothing(oSocket) OrElse oSocket.State <> WebSocketState.Open Then
            Exit Sub
        End If

        Dim sMsg As String = txtMsg.Text
        If sMsg = "" Then
            Exit Sub
        End If

        txtMsg.Text = ""

        Await oSocket.SendAsync(New ArraySegment(Of Byte)_
                               (System.Text.Encoding.UTF8.GetBytes(sMsg)), _
                                System.Net.WebSockets.WebSocketMessageType.Text, _
                                True, Nothing)
    End Sub

    Private Sub btnClose_Click(sender As Object, e As EventArgs) Handles btnClose.Click
        LogOff()
    End Sub

    Private Async Sub LogOff()

        btnOpen.Enabled = True
        btnSend.Enabled = False
        btnClose.Enabled = False
        lbStatus.ForeColor = Color.FromArgb(255, 0, 0)
        selUsers.Items.Clear()

        If IsNothing(oSocket) OrElse oSocket.State <> WebSocketState.Open Then
            Exit Sub
        End If

        Await oSocket.CloseAsync(WebSocketCloseStatus.Empty, "", _
                                 Threading.CancellationToken.None)
    End Sub

    Private Sub btnRefreshUsers_Click(sender As Object, e As EventArgs) _
                                      Handles btnRefreshUsers.Click
        RefreshUsers()
    End Sub

    Private Sub RefreshUsers()
        selUsers.Items.Clear()

        Dim sServer As String = txtURL.Text 'localhost/WebSocket
        If sServer = "" Then
            MsgBox("Server URL is missing")
            Exit Sub
        End If

        Dim sProtocol As String = "http"
        If chkSSL.Checked Then
            sProtocol = "https"
        End If

        Dim sUrl As String = sProtocol & "://" & sServer & "/Chat.aspx?getUsers2=1"
        Dim sUsers As String = GetData(sUrl)
        Dim oUsers As String() = sUsers.Split(vbCrLf)

        For i = 0 To oUsers.Length - 1
            Dim sUser As String = oUsers(i)
            selUsers.Items.Add(sUser)
        Next
    End Sub

    Private Function GetData(ByVal sUrl As String) As String
        Dim oHttpWebRequest As System.Net.HttpWebRequest
        Dim oHttpWebResponse As System.Net.HttpWebResponse

        oHttpWebRequest = CType(System.Net.WebRequest.Create(sUrl), _
                          System.Net.HttpWebRequest)
        oHttpWebRequest.Timeout = 1000 * 60 * 60 'Hour
        oHttpWebRequest.KeepAlive = False
        oHttpWebRequest.Method = "POST"
        oHttpWebRequest.ContentLength = 0

        Try
            oHttpWebResponse = CType(oHttpWebRequest.GetResponse(), _
                               System.Net.HttpWebResponse)
        Catch ex As Exception
            Return ex.Message & vbCrLf & ex.StackTrace()
        End Try

        'Read Request
        Dim oStreamReader As IO.StreamReader = _
            New IO.StreamReader(oHttpWebResponse.GetResponseStream, _
            System.Text.UTF8Encoding.UTF8)
        Dim sHTML As String = oStreamReader.ReadToEnd

        oStreamReader.Close()
        oHttpWebResponse.Close()
        oHttpWebRequest.Abort()

        Return sHTML
    End Function

    Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) _
        Handles Me.Closing
        LogOff()
    End Sub

End Class

Points of Interest

Next step would be to add file upload, JavaScript notification, audio and video features. :)

History

  • 1st August, 2022: Version 1 created

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)