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
原文地址:https://www.cnblogs.com/heyiping/p/14949234.html