1. 程式人生 > >使用.NET Core和Vue搭建WebSocket聊天室

使用.NET Core和Vue搭建WebSocket聊天室

  Hi,大家好,我是Payne,歡迎大家關注我的部落格,我的部落格地址是:https://qinyuanpei.github.io。今天這篇部落格,我們來說說WebSocket。各位可能會疑惑,為什麼我會突然間對WebSocket感興趣,這是因為最近接觸到了部分“實時”的業務場景,譬如:使用者希望在遠端視訊通話過程中,實時地監控接入方的通話狀態,實時地將接入方的響應時間、通話時長以及接通率等資訊推送到後臺。與此同時,使用者可以通過監控平臺看到實時變化著的圖表。坦白地講,這種業務場景陌生嗎?不,每一年的雙11,都能見到小夥伴們實時地“剁手”。所以,在今天這篇文章中,我們會以WebSocket聊天室為例,來講解如何基於WebSocket構建實時應用。

WebSocket概述

  WebSocket是HTML5標準中的一部分,從Socket這個字眼我們就可以知道,這是一種網路通訊協議。WebSocket是為了彌補HTTP協議的不足而產生的,我們知道,HTTP協議有一個重要的缺陷,即:請求只能由客戶端發起。這是因為HTTP協議採用了經典的請求-響應模型,這就限制了服務端主動向客戶端推送訊息的可能。與此同時,HTTP協議是無狀態的,這意味著連線在請求得到響應以後就關閉了,所以,每次請求都是獨立的、上下文無關的請求。這種單向請求的特點,註定了客戶端無法實時地獲取服務端的狀態變化,如果服務端的狀態發生連續地變化,客戶端就不得不通過“輪詢”的方式來獲知這種變化。毫無疑問,輪詢的方式不僅效率低下,而且浪費網路資源,在這種背景下,WebSocket應運而生。

  WebSocket協議最早於2008年被提出,並於2011年成為國際標準。目前,主流的瀏覽器都已經提供了對WebSocket的支援。在WebSocket協議中,客戶端和伺服器之間只需要做一次握手操作,就可以在客戶端和伺服器之間實現雙向通訊,所以,WebSocket可以作為伺服器推送的實現技術之一。因為它本身以HTTP協議為基礎,所以對HTTP協議有著更好的相容性,無論是通訊效率還是傳輸的安全性都能得到保證。WebSocket沒有同源限制,客戶端可以和任意伺服器端進行通訊,因此具備通過一個單一連線來支援上下游通訊的能力。從本質上來講,WebSocket是一個在握手階段使用HTTP協議的TCP/IP協議,換句話說,一旦握手成功,WebSocket就和HTTP協議再無瓜葛,下圖展示了它與HTTP協議的區別:

HTTP與WebSocket的區別

構建一個聊天室

  OK,在對WebSocket有了一個基本的認識以後,接下來,我們以一個最簡單的場景來體驗下WebSocket。這個場景是什麼呢?你已經知道了,答案就是網路聊天室。這是一個非常典型的實時場景。這裡我們分為服務端實現和客戶端實現,其中:服務端實現自豪地採用.NET Core,而客戶端實現採用Vue的雙向繫結特性。現在是公元2018年了,當jQuery已成往事,操作DOM這種事情交給框架去做就好,而且我本人很喜歡MVVM這種模式,Vue的漸進式框架,非常適合我這種不會寫ES6的偽前端。

.NET Core與中介軟體

  關於.NET Core中對WebSocket的支援,這裡主要參考了官方文件,在這篇文件中,演示了一個最基本的Echo示例,即服務端如何接收客戶端訊息並返回訊息給客戶端。這裡,我們首先需要安裝Microsoft.AspNetCore.WebSockets這個庫,直接通過Visual Studio Code內建的終端安裝即可。接下來,我們需要在Startup類的Configure方法中新增WebSocket中介軟體:

app.UseWebSockets()

更一般地,我們可以配置以下兩個配置,其中,KeepAliveInterval表示向客戶端傳送Ping幀的時間間隔;ReceiveBufferSize表示接收資料的緩衝區大小:

var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
    ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets(webSocketOptions);

  好了,那麼怎麼接收一個來自客戶端的請求呢?這裡以官方文件中的示例程式碼為例來說明。首先,我們需要判斷下請求的地址,這是客戶端和服務端約定好的地址,預設為/,這裡我們以/ws為例;接下來,我們需要判斷當前的請求上下文是否為WebSocket請求,通過context.WebSockets.IsWebSocketRequest來判斷。當這兩個條件同時滿足時,我們就可以通過context.WebSockets.AcceptWebSocketAsync()方法來得到WebSocket物件,這樣就表示“握手”完成,這樣我們就可以開始接收或者傳送訊息啦。

if (context.Request.Path == "/ws")
{
    if (context.WebSockets.IsWebSocketRequest)
    {
        WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
        //TODO
    }
});

  一旦建立了Socket連線,客戶端和服務端之間就可以開始通訊,這是我們從Socket中收穫的經驗,這個經驗同樣適用於WebSocket。這裡分別給出WebSocket傳送和接收訊息的實現,並針對程式碼做簡單的分析。

private async Task SendMessage<TEntity>(WebSocket webSocket, TEntity entity)
{
    var Json = JsonConvert.SerializeObject(entity);
    var bytes = Encoding.UTF8.GetBytes(Json);

    await webSocket.SendAsync(
        new ArraySegment<byte>(bytes),
        WebSocketMessageType.Text,
        true,
        CancellationToken.None
    );
}

  這裡我們提供一個泛型方法,它負責對訊息進行序列化並轉化為byte[],最終呼叫SendAsync()方法傳送訊息。與之相對應地,客戶端會在onmessage()回撥中就會接受到訊息,這一點我們放在後面再說。WebSocket接收訊息的方式,和傳統的Socket非常相似,我們需要將位元組流迴圈讀取到一個快取區裡,直至所有資料都被接收完。下面給出基本的程式碼示例:

var buffer = new ArraySegment<byte>(new byte[bufferSize]);
var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
while (!result.EndOfMessage)
{
    result = await webSocket.ReceiveAsync(buffer, default(CancellationToken));
}

var json = Encoding.UTF8.GetString(buffer.Array);
json = json.Replace("\0", "").Trim();
return JsonConvert.DeserializeObject<TEntity>(json, new JsonSerializerSettings()
{
    DateTimeZoneHandling = DateTimeZoneHandling.Local
});

  雖然不大清楚,為什麼這裡反序列化後的內容中會有大量的\0,以及這個全新的型別ArraySegment到底是個什麼鬼,不過程式設計師的一生無非都在糾結這樣兩個問題,“it works” 和 “it doesn’t works”,就像人生裡會讓你糾結的無非是”她喜歡你“和”她不喜歡我“這樣的問題。有時候,這樣的問題簡直就是玄學,五柳先生好讀書而不求甚解,我想這個道理在這裡同樣適用,截止到我寫這篇部落格前,這個程式碼一直工作得很好,所以,這兩個問題我們可以暫時先放在一邊,因為眼下還有比這更為重要的事情。

  通過這篇文件,我們可以非常容易地構建出一個”實時應用“,可是它離我們這篇文章中的目標依然有點距離,如果各位足夠細心的話,就會發現這樣一個問題,即示例中的程式碼都是寫在app.Use()方法中的,這樣會使我們的Startup類顯得臃腫,而熟悉OWIN或者ASP.NET Core的朋友,就會知道Startup類是一個非常重要的東西,我們通常會在這裡配置相關的元件。在ASP.NET Core中,我們可以通過Configure()方法來為IApplicationBuilder增加相關元件,這種元件通常被稱為中介軟體。那麼,什麼是中介軟體呢?

中介軟體示意圖

  從這張圖中可以看出,中介軟體實際上是指在HTTP請求管道中處理請求和響應的元件,每個元件都可以決定是否要將請求傳遞給下一個元件,比如身份認證、日誌記錄就是最為常見的中介軟體。在ASP.NET Core中,我們通過app.Use()方法來定義一個Func

app.Use(async (context,next)=>
{
    await context.Response.WriteAsync("這是第一個中介軟體\r\n");
    await next();
});

app.Use(async (context,next)=>
{
    await context.Response.WriteAsync("這是第二個中介軟體\r\n");
    await next();
});

app.Use(async (context,next)=>
{
    await context.Response.WriteAsync("這是第三個中介軟體\r\n");
    await next();
});

  通過Postman或者任意客戶端發起請求,我們就可以得到下面的結果,現在想象一下,如果我們在第一種中介軟體中不呼叫next()會怎麼樣呢?答案是中介軟體之間的鏈路會被打斷,這意味著後續的第二個、第三個中介軟體都不會被執行。什麼時候我們會遇到這種場景呢?當我們的認證中介軟體認為一個請求非法的時候,此時我們不應該讓使用者訪問後續的資源,所以直接返回403對該請求進行攔截。在大多數情況下,我們需要讓請求隨著中介軟體的鏈路傳播下去,所以,對於每一箇中間件來說,除了完成自身的處理邏輯以外,還至少需要呼叫一次next(),以保證下一個中介軟體會被呼叫,這其實和職責鏈模式非常相近,可以讓資料在不同的處理管道中進行傳播。

ASP.NET Core中介軟體示例

  OK,這裡我們繼續遵從這個約定,將整個聊天室相關的邏輯寫到一箇中間件裡,這樣做的好處是,我們可以將不同的WebSocket互相隔離開,同時可以為我們的Startup類”減負“。事實證明,這是一個正確的決定,在開發基於WebSocket的彈幕功能時,我們就是用這種方式開發了新的中介軟體。這裡,我們給出的是WebSocketChat中介軟體中最為關鍵的部分,詳細的程式碼我已經放在Github上啦,大家可以參考WebSocketChat類,其基本原理是:使用一個字典來儲存每一個聊天室中的會話(Socket),當用戶開啟或者關閉一個WebSocket連線時,會向伺服器端傳送一個事件(Event),這樣客戶端中持有的使用者列表將被更新,而根據傳送的訊息,可以決定這條訊息是被髮給指定聯絡人還是群發:

public async Task Invoke(HttpContext context)
{
    if (!IsWebSocket(context))
    {
        await _next.Invoke(context);
        return;
    }

    var userName = context.Request.Query["username"].ToArray()[0];
    var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    while (webSocket.State == WebSocketState.Open)
    {
         var entity = await Receiveentity<MessageEntity>(webSocket);
         switch (entity.Type)
         {
             case MessageType.Chat:
                  await HandleChat(webSocket, entity);
                  break;
             case MessageType.Event:
                  await HandleEvent(webSocket, entity);
                  break;
         }
    }

    await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Close", default(CancellationToken));
}

  其中,HandleEvent負責對事件進行處理,HandleChat負責對訊息進行處理。當有使用者加入聊天室的時候,首先會向所有客戶端廣播一條訊息,告訴大家有新使用者加入了聊天室,與此同時,為了讓大家可以和新使用者進行通訊,必須將新的使用者列表推送到客戶端。同理,當有使用者離開聊天室的時候,伺服器端會有類似的事件推送到客戶端。事件同樣是基於訊息來實現的,不過這兩種採用的資料結構不同,具體大家可以通過原始碼來了解。傳送訊息就非常簡單啦,給指定使用者傳送訊息是通過使用者名稱來找WebSocket物件,而群發訊息就是遍歷字典中的所有WebSocket物件,這一點我們不再詳細說啦!

Vue驅動的客戶端

  在實現服務端的WebSocket以後,我們就可以著手客戶端的開發啦!這裡我們採用原生的WebSocket API來開發相關功能。具體來講,我們只需要例項化一個WebSocket類,並設定相應地回撥函式就可以了,我們一起來看下面的例子:

var username = "PayneQin"
var websocket = new WebSocket("ws://localhost:8002/ws?username=" + username);

  這裡我們使用/s這個路由來訪問WebSocket,相應地,在服務端程式碼中我們需要判斷context.Request.Path,WebSocket在握手階段是基於HTTP協議的,所以我們可以以QueryString的形式給後端傳遞一個引數,這裡我們需要一個使用者名稱,它將作為服務端儲存WebSocket時的一個鍵。一旦建立了WebSocket,我們就可以通過回撥函式來監聽伺服器端的響應,或者是傳送訊息給伺服器端。主要的回撥函式有onopen、onmessage、onerror和onclose四個,基本使用方法如下:

websocket.onopen = function () {
    console.log("WebSocket連線成功");
};

websocket.onmessage = function (event) {
    console.log("接收到服務端訊息:" + event.data)
};

websocket.onerror = function () {
    console.log("WebSocket連線發生錯誤");
};

websocket.onclose = function () {
console.log("WebSocket連線關閉");
};

  原生的WebSocket API只有兩個方法,即send()和close(),這兩個方法非常的簡單,我們這裡不再說明。需要說明的是,客戶端使用了Vue來做介面相關的繫結,作為一個不會寫CSS、不會寫ES6的偽前端,我做了一個相當簡潔(簡陋)的前端頁面,下面給出主要的頁面結構,ViewModel層的程式碼比較多,大家可以參考這裡

<div id="app">
    Hi,{{ username }}。歡迎來到WebSocket聊天室!
    <hr/> 傳送給:
    <select v-model="sendTo">
        <option value="All">全部</option>
        <option v-for="user in userList" :value="user">{{user}}</option>
    </select>
    <hr/>
    <input id="text" type="text" v-model="message" />
    <button v-on:click="sendMessage">傳送訊息</button>
    <hr/>
    <button v-on:click="openWebSocket">開啟WebSocket連線</button>
    <button v-on:click="closeWebSocket">關閉WebSocket連線</button>
    <button v-on:click="clearMessageList">清空聊天記錄</button>
    <hr/>
    <div id="messageList" v-html="messageList">
        {{ messageList }}
    </div>
</div>

  下面是實際的執行效果,果然是非常簡潔呢,哈哈:laughing:

WebSocket聊天室展示

再看Websocket

  好了,我們花了如此大的篇幅來講WebSocket,那麼你對WebSocket瞭解了多少呢?或許通過這個聊天室的例項,我們對WebSocket有了一個相對直觀的認識,可你是否想過換一個角度來認識它呢?我們說過,WebSocket是以HTTP協議為基礎的,那麼至少可以在握手階段捕獲到相關請求吧!果斷在Chrome中開啟”開發者工具“,在面板上選擇監聽”WebSocket”,然後我們就會得到下面的內容。

WebSocket的祕密-請求

  相比HTTP協議,WebSocket在握手階段的請求有所變化,主要體現在Upgrade、Connection這兩個欄位,以及Sec-WebSocket系列的這些欄位。下面來分別解釋下這些欄位的含義,Upgrade和Connection這兩個欄位,是最為關鍵的兩個欄位,它的目的是告訴Apache、Nginx這些伺服器,這是一個WebSocket請求。接下來,是Sec-WebSocket-Key、Sec-WebSocket-Protocol和Sec-WebSocket-Version這三個欄位,其中Sec-WebSocket-Key是一個由瀏覽器採用Base64演算法隨機生成的字串,目的是驗證伺服器是否真的支援WebSocket;Sec-WebSocket-Protocol則是一個由使用者指定的字串,目的是區分同一URL下,不同服務所需要的協議;Sec-WebSocket-Version是告訴伺服器瀏覽器支援的WebSocket版本,標準規定9-12的版本號是保留欄位,所以在這裡我們看到的版本號是13.

WebSocket的祕密-響應

  那麼,對於這個瀏覽器發起的這個請求,服務端是如何做出響應的呢?這就要來看看服務端返回的內容。 和客戶端發起的請求類似,服務端返回的內容中依然會有Upgrade和Connection這兩個欄位,它們和請求中的含義是完全一致的。這裡需要說明的是Sec-WebSocket-Accept這個欄位,我們前面提到,瀏覽器會通過WebSocket-Key檢驗伺服器是否真的支援WebSocket,具體怎麼檢驗呢?是通過下面的演算法。除此之外,一個特殊的地方是這個Response的狀態碼是101,這表示服務端說:下面我們就按照WebSocket協議來通訊吧!當然,一個更為殘酷的現實是,從這裡開始,就不再是HTTP協議的勢力範圍了啊:

sec-websocket-accept = base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

本文小結

  這篇文章選取了“實時應用”這樣一個業務場景作為切入點,引出了本文的主題——WebSocket。WebSocket是一種建立在HTTP協議基礎上的雙向通訊協議,它彌補了以“請求-響應”模型為基礎的HTTP協議先天上的不足,客戶端無需再通過“輪詢”這種方式來獲取服務端的狀態變化。WebSocket在完成“握手”後,即可以長連線的方式在客戶端和服務端間構建雙向通道,因而WebSocket可以在實時應用場景下,作為伺服器推送技術的一種方案選擇。本文以一個WebSocket聊天室的案例,來講解WebSocket在實際專案中的應用,在這裡我們使用ASP.NET Core來完成服務端WebSocket的實現,而客戶端選用原生WebSocket API和Vue來實現,在此基礎上,我們講解了ASP.NET Core下中介軟體的概念,並將伺服器端WebSocket以中介軟體的形式實現。在下一篇文章中,我們將偏重於伺服器端的資料推送,客戶端將作為資料展現層而存在。好了,以上就是這篇文章的全部內容啦,謝謝大家,讓我們一起期待下一篇文章吧!

相關推薦

使用.NET CoreVue搭建WebSocket聊天

  Hi,大家好,我是Payne,歡迎大家關注我的部落格,我的部落格地址是:https://qinyuanpei.github.io。今天這篇部落格,我們來說說WebSocket。各位可能會疑惑,為什麼我會突然間對WebSocket感興趣,這是因為最近接觸到了部

websocket搭建聊天

說明 原理 example port adc type htm 消息 var 在前後端數據交互的時候我們經常使用的是ajax,用的是傳統的http協議,而http協議有個致命的缺點,就是請求一結束,連接就斷開了, 我們為了保持這個鏈接的,通常會使用cookie,而自從h5出

Go websocket 聊天的詳細實現詳細分析

websocket 聊天室資料結構分析 首先要做一個聊天室我們需要把所有的連線資訊都儲存下來 所以就需要有一個客戶端 client 的 manager ,manager 裡應該儲存所有的client 資訊 所以在我們的程式裡定義了 ClientManager 這個

VS2017搭建ASP.Net CoreAngular5專案

本文是使用Angular5TemplateCore開發一個適用於ASP.NET Core的Angular 5應用程式。我們來詳細看看 先決條件 確保您已經在計算機上安裝了所有先決條件。如果沒有,那麼先下載並安裝所有。 首先,從這個連結下載並安裝Visual Stud

基於Html5 websocketPython的線上聊天

握手協議:request中有三個隨機的key值,頭部有兩個,後面body裡是長度為8位元組的key3(括號裡的文字是提示,還有字元間的冒號也是為了看上去清晰才加上的,真正傳輸是沒有的),以此向server傳送一個challenge,server需要根據這三個key計算出一個token,在響應中發回給clien

基於tcpqt的簡單聊天搭建

使用Qt庫中的 <QTcpServer>  和<QTcpSocket>類實現區域網絡下的聊天室。 分為服務端和客戶端; 服務端接收來自各個客戶端的資訊,併發送到所有客戶端; 客戶端用於使用者登陸及聊天。 客戶端: 使用<QTcpSo

php基於websocket搭建簡易聊天(socket)

前言 http連線分為短連線和長連線。短連線一般可以用ajax實現,長連線就是websocket。短連線實現起來比較簡單,但是太過於消耗資源。websocket高效不過相容存在點問題。websocket是html5的資源 前端 //連線socket

.net core WPF 開發升訊威線上客服與營銷系統:使用 WebSocket 實現訪客端通訊

本系列文章詳細介紹使用 .net core 和 WPF 開發 升訊威線上客服與營銷系統 的過程。本產品已經成熟穩定並投入商用。 線上演示環境:[https://kf.shengxunwei.com](https://kf.shengxunwei.com) 注意:演示環境僅供演示交流與評估,不保證 7x24 小

php-GatewayWorker搭建實時聊天

publish span ons 文件的 java 名稱 time() eva 一個 ├── Applications // 這裏是所有開發者應用項目 │ └── YourApp // 其中一個項目目錄,目錄名可以自定義 │ ├── Events.php

.Net Corejexus配置HTTPS服務

spa log文件 https 個人 line fig rap 方式 phy 花了幾天時間,看了好多篇博客,終於搞定了網站的HTTPS服務,借此寫篇博客,來讓有需要的朋友少走彎路。 一、環境介紹   1、Linux下在Docker容器中部署好了一個網站,該網站需要通過外部提

ASP.NET Core 一步步搭建個人網站(7)_Linux系統移植

window std bce stat 能夠 rpm 設置 with err 摘要 考慮我們為什麽要選擇.NET Core? 因為它面向的是高性能服務器開發,拋卻了 AspNet 的臃腫組件,非常輕量,加上微軟的跨平臺戰略,對 Docker 的親和性,對於開發人員也非常友好

使用ASP.NET SignalR實現一個簡單的聊天

spl 記錄 歷史 undefine reat 語句 關鍵字 pda name  前言   距離我寫上一篇博客已經又過了一年半載了,時間過得很快,一眨眼,就把人變得滄桑了許多。青春是短暫的,知識是無限的。要用短暫的青春,去學無窮無盡的知識,及時當勉勵,歲月不待人。今天寫個隨

通過 Docker Compose 組合 ASP NET Core SQL Server

dock syn point mode 只需要 acc uil test usr 目錄 Docker Compose 簡介 安裝 WebApi 項目 創建項目 編寫Dockfile Web MVC 項目 創建項目 編寫Dockfile 編寫 docker-compo

dotnet core webapi +vue 搭建前後端完全分離web架構(一)

cal ade 跨平臺 onf env ans loading p s response 摘要: 架構 服務端采用 dotnet core webapi 前端采用: Vue + router +elementUI+axios 問題 使用前後端完全

.NET Core.NET Standard有什麽不同

cos 還需要 log http item lod 異常處理 req 一個 原文:.NET Core和.NET Standard有什麽不同 近日,微軟發布了.NET Core 2.0,但是開發

PHP簡單實現WebSocket(聊天)

在PHP中,開發者需要考慮的東西比較多,從socket的連線、建立、繫結、監聽等都需要開發者自己去操作完成,對於初學者來說,難度方面也挺大的,所以本文的思路如下: 1、socket協議的簡介 2、介紹client與server之間的連線原理 3、PHP中建立socket的過程講解 4

使用ASP.NET MVC Web SinalR 構建單身聊天(一)

前言:本系列的頭章,想要帶大家一起學習Web SignalR,那它是什麼呢?ASP .NET SignalR 是一個ASP .NET 下的類庫,可以在ASP .NET 的Web專案中實現實時通訊。什麼是實時通訊的Web呢?就是讓客戶端(Web頁面)和伺服器端可以互相通知訊息及呼叫方法,當然這是實時操作的。

用 Netty 實現 WebSocket 聊天功能

WebSocket 是 H5 的一種技術,目前有很多語言都有相應的實現,之前有用 WebSocket 實現過 Java 和安卓,IOS 通訊的專案。想來也該分享一下,看過不少專案要實現頁面資料實時更新的操作,有用輪詢有用 Socket 連結的,當然也不排除有很多前端其他技術可以實現,WebSocke

Asp.net Core 類庫讀取配置檔案資訊

Asp.net Core 和類庫讀取配置檔案資訊 看乾貨請移步至.net core 讀取配置檔案公共類 首先開一個腦洞,Asp.net core 被使用這麼長時間了,但是關於配置檔案(json)的讀取,微軟官方似乎並沒有給出像.net framework讀取web.config那樣簡單且完美。嚴重懷

SpringBoot 搭建簡單聊天

SpringBoot 搭建簡單聊天室(queue 點對點) 1、引用 SpringBoot 搭建 WebSocket 連結   https://www.cnblogs.com/yi1036943655/p/10089100.html 2、整合Spring Security package com