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.
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.
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.
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.
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
oSockets(sUserId) = oSocket
context.Application("Sockets") = oSockets
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
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)
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())
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
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
Await oSocket.CloseAsync(WebSocketCloseStatus.Empty, "", _
Threading.CancellationToken.None)
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.
<%@ 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
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 +
"/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(
};
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()">↻</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.
It opens a WebSocket
connection and waits for the massage to arrive.
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
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
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)
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
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
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
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