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 }
前端是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>