65.9K
CodeProject 正在变化。 阅读更多。
Home

带 Web Socket 的聊天应用

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2022 年 8 月 1 日

CPOL

1分钟阅读

viewsIcon

9593

downloadIcon

709

尝试创建一个最小的聊天应用程序,使用 Web Socket。

引言

这个应用程序是我尝试使用 WebSockets 创建的一个最小的聊天应用程序。我试图使用最少的代码来展示如何使用 WebSockets。WebSockets 是一种允许您将数据从 Web 服务器推送到 Web 浏览器的技术。

背景

要使用 WebSockets,您的 Web 服务器需要安装 ASP.NET 4.5 或更高版本以及 WebSocket 协议,在 添加角色和功能 > 服务器角色 > Web 服务器 > Web 服务器 > 应用程序开发 > WebSocket 协议 下安装。

Using the Code

要设置部署,请将 WebSocket.zip 解压缩到 Web 服务器上的 C:\inetpub\wwwroot\WebSocket。打开 IIS 控制台 并创建一个名为 WebSocket 的虚拟目录。

接下来,将您的浏览器指向 https:///WebSocket/Chat.aspx.

Web 部署包含两个文件:Handler1.ashxChat.aspxHandler1.ashx 处理程序文件处理 WebSockets 请求。它使用 HandleSocketRequest 函数处理 WebSockets 请求。Async 函数在套接字连接打开时循环。它使用 Await 等待消息,然后将消息广播给所有已注册的用户。

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 文件允许您登录并向所有连接的用户广播消息。它是一个 ASP.NET Web 表单,用于获取活动用户列表,并使用 JavaScript 将消息发送到 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
                    '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>

作为奖励,我创建了一个 Windows 应用程序,它连接到 Handler1.ashx,并以与 Chat.aspx 几乎相同的方式运行,只是使用 VB.NET 代替 JavaScript。

它打开一个 WebSocket 连接并等待消息到达。

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

关注点

下一步将是添加文件上传、JavaScript 通知、音频和视频功能。:)

历史

  • 2022 年 8 月 1 日:创建版本 1
© . All rights reserved.