Web端線上實時聊天,基於WebSocket(前後端分離)
阿新 • • 發佈:2021-06-29
這是一個簡易的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;View Code6 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 }
前端是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