1. 程式人生 > 其它 >Web端線上實時聊天,基於WebSocket(前後端分離)

Web端線上實時聊天,基於WebSocket(前後端分離)

這是一個簡易的Demo,已經實現了基礎的功能

之前一直想實現一個實時聊天的系統,一直沒有去實踐他。有一天吃飯的時候掃碼點菜,幾個人點菜能夠實時更新,當時就在想,這應該是同一種技術。

剛好前段時間專案上用到了mqtt和signalR。現在抽個時間自己在梳理一遍。

下面是效果

直接上程式碼。

後端是WebApi專案,.NET Framework 4.5

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Net;
  5 using System.Net.Http;
6 using System.Web.Http; 7 using System.Net.WebSockets; 8 using System.Web; 9 using System.Web.WebSockets; 10 using System.Text; 11 using System.Threading; 12 using System.Threading.Tasks; 13 14 namespace StudentSys.WebApi.Controllers 15 { 16 /// <summary> 17 /// 離線訊息 18 ///
</summary> 19 public class MessageInfo 20 { 21 public MessageInfo(DateTime _MsgTime, ArraySegment<byte> _MsgContent) 22 { 23 MsgTime = _MsgTime; 24 MsgContent = _MsgContent; 25 } 26 public DateTime MsgTime { get; set; }
27 public ArraySegment<byte> MsgContent { get; set; } 28 } 29 30 [RoutePrefix("api/socket")] 31 public class WebSocketController : ApiController 32 { 33 private static Dictionary<string, WebSocket> CONNECT_POOL = new Dictionary<string, WebSocket>();//使用者連線池 34 private static Dictionary<string, List<MessageInfo>> MESSAGE_POOL = new Dictionary<string, List<MessageInfo>>();//離線訊息池 35 36 [HttpGet] 37 [Route("connect")] 38 public HttpResponseMessage Connect() 39 { 40 //在服務端接受web socket請求,傳入的函式作為web socket的處理函式,待web socket建立後該函式會被呼叫, 41 //在該函式中可以對web socket進行訊息收發 42 HttpContext.Current.AcceptWebSocketRequest(ProcessChat); 43 //構造同意切換至web socket的response 44 return Request.CreateResponse(HttpStatusCode.SwitchingProtocols); 45 } 46 47 private async Task ProcessChat(AspNetWebSocketContext context) 48 { 49 WebSocket socket = context.WebSocket; 50 string user = context.QueryString["userid"].ToString(); 51 52 try 53 { 54 #region 使用者新增連線池 55 //第一次open時,新增到連線池中 56 if (!CONNECT_POOL.ContainsKey(user)) 57 CONNECT_POOL.Add(user, socket);//不存在,新增 58 else 59 if (socket != CONNECT_POOL[user])//當前物件不一致,更新 60 CONNECT_POOL[user] = socket; 61 #endregion 62 63 #region 離線訊息處理 64 if (MESSAGE_POOL.ContainsKey(user)) 65 { 66 List<MessageInfo> msgs = MESSAGE_POOL[user]; 67 foreach (MessageInfo item in msgs) 68 { 69 await socket.SendAsync(item.MsgContent, WebSocketMessageType.Text, true, CancellationToken.None); 70 } 71 MESSAGE_POOL.Remove(user);//移除離線訊息 72 } 73 #endregion 74 75 string descUser = string.Empty;//目的使用者 76 while (true) 77 { 78 if (socket.State == WebSocketState.Open) 79 { 80 ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]); 81 WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None); 82 83 #region 訊息處理(字元擷取、訊息轉發) 84 try 85 { 86 #region 關閉Socket處理,刪除連線池 87 if (socket.State != WebSocketState.Open)//連線關閉 88 { 89 if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//刪除連線池 90 break; 91 } 92 #endregion 93 94 string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);//傳送過來的訊息 95 string[] msgList = userMsg.Split('|'); 96 if (msgList.Length == 2) 97 { 98 if (msgList[0].Trim().Length > 0) 99 descUser = msgList[0].Trim();//記錄訊息目的使用者 100 buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msgList[1])); 101 } 102 else 103 buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMsg)); 104 105 if (CONNECT_POOL.ContainsKey(descUser))//判斷客戶端是否線上 106 { 107 WebSocket destSocket = CONNECT_POOL[descUser];//目的客戶端 108 if (destSocket != null && destSocket.State == WebSocketState.Open) 109 await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None); 110 } 111 else 112 { 113 Task.Run(() => 114 { 115 if (!MESSAGE_POOL.ContainsKey(descUser))//將使用者新增至離線訊息池中 116 MESSAGE_POOL.Add(descUser, new List<MessageInfo>()); 117 MESSAGE_POOL[descUser].Add(new MessageInfo(DateTime.Now, buffer));//新增離線訊息 118 }); 119 } 120 } 121 catch (Exception exs) 122 { 123 //訊息轉發異常處理,本次訊息忽略 繼續監聽接下來的訊息 124 } 125 #endregion 126 } 127 else 128 { 129 break; 130 } 131 }//while end 132 } 133 catch (Exception ex) 134 { 135 //整體異常處理 136 if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user); 137 } 138 } 139 140 public bool IsReusable 141 { 142 get 143 { 144 return false; 145 } 146 } 147 } 148 }
View Code

前端是vue專案

  1 <template>
  2   <div class="page">
  3     <van-cell-group>
  4       <van-field v-model="userid" label="你的暱稱" placeholder="請輸入" />
  5     </van-cell-group>
  6     <van-button plain type="info" @click="lianjie" size="mini"
  7       >開始聊天</van-button
  8     >
  9     <van-button plain type="info" @click="btnDisconnect" size="mini"
 10       >關閉聊天</van-button
 11     >
 12 
 13     <van-cell-group>
 14       <van-field v-model="party_userid" label="對方暱稱" placeholder="請輸入" />
 15     </van-cell-group>
 16     <van-field
 17       v-model="sendstr"
 18       center
 19       clearable
 20       label="訊息"
 21       placeholder="請輸入內容"
 22       type="textarea"
 23       maxlength="50"
 24       show-word-limit
 25     >
 26       <template #button>
 27         <van-button type="primary" @click="btnSend" size="mini"
 28           >傳送</van-button
 29         >
 30       </template>
 31     </van-field>
 32 
 33     <!-- 訊息 -->
 34     <van-divider
 35       :style="{ color: '#1989fa', borderColor: '#1989fa', padding: '0 16px' }"
 36     >
 37       訊息
 38     </van-divider>
 39     <van-steps direction="vertical">
 40       <van-step v-for="item in news" :key="item.time">
 41         <h3>{{ item.text }}</h3>
 42         <p>{{ item.time }}</p>
 43       </van-step>
 44     </van-steps>
 45   </div>
 46 </template>
 47 
 48 <script>
 49 var ws;
 50 export default {
 51   data() {
 52     return {
 53       userid: "",
 54       party_userid: "",
 55       sendstr: "",
 56 
 57       news: [],
 58     };
 59   },
 60   methods: {
 61     lianjie() {
 62       let url = "ws://47.100.30.65/MobileGadgetsApi/api/socket/connect";
 63       ws = new WebSocket(`${url}?userid=${this.userid}`);
 64 
 65       let that = this;
 66       ws.onopen = function () {
 67         // Toast("提示內容");
 68         console.log("Connected!");
 69       };
 70       ws.onmessage = function (result) {
 71         console.log(result);
 72         that.news.unshift({
 73           text: result.data,
 74           time: that.$moment().format("YYYY-MM-DD HH:mm:ss"),
 75         });
 76       };
 77       ws.onerror = function (error) {
 78         console.log("error");
 79         console.log(error);
 80         console.log(error.data);
 81         console.log("errorend");
 82       };
 83       ws.onclose = function () {
 84         console.log("Disconnected!");
 85       };
 86     },
 87 
 88     btnDisconnect() {
 89       ws.close();
 90     },
 91 
 92     btnSend() {
 93       if (ws.readyState == WebSocket.OPEN) {
 94         ws.send(`${this.party_userid}|${this.sendstr}`);
 95       } else {
 96         console.log("Connection is Closed!");
 97         // $("messageSpan").text("Connection is Closed!");
 98       }
 99     },
100   },
101 };
102 </script>
103 
104 <style lang="scss" scoped>
105 </style>
View Code