这是一个简易的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>

Web端在线实时聊天,基于WebSocket(前后端分离)的更多相关文章

  1. 基于SpringBoot前后端分离的点餐系统

    基于SpringBoot前后端分离的点餐系统 开发环境:主要采用Spring boot框架和小程序开发 项目简介:点餐系统,分成卖家端和买家端.买家端使用微信小程序开发,实现扫码点餐.浏览菜单.下单. ...

  2. websocket+前后端分离+https的nginx配置

    后端服务路径: 172.168.0.2:8080 172.168.0.2:7080 前端目录(html + css + js): /root/apps/mzsg-web 1.修改 /etc/nginx ...

  3. 从壹开始前后端分离 [ Vue2.0+.NET Core2.1] 十四 ║ VUE 计划书 & 我的前后端开发简史

    ---新内容开始--- 番外 大家周一好呀,又是元气满满的一个周一呀!感谢大家在周一这个着急改Bug的黄金时期,抽出时间来看我的博文哈哈哈,时间真快,已经到第十四篇博文了,也很顺顺(跌跌)利利 (撞撞 ...

  4. Yii框架和Vue的完美结合完成前后端分离项目

    背景说明 本文假设你对Yii和Vue都比较熟悉,至少都在项目里用过,另外笔者新人,以后不定时放一些干货,欢迎程序媛关注 Yii是一个PHP全端框架,典型的mvc的项目结构,后端接口都是一个控制器里放了 ...

  5. 创建您的 ActiveReports Web端在线报表设计器

    概述 ActiveReports Web端在线报表设计器已经正式上线!看到它这么帅气.实用,你是不是也想自己动手创建一个? 现在我们就来教您,如何创建一个简单的 ActiveReports Web端在 ...

  6. (转)也谈基于NodeJS的全栈式开发(基于NodeJS的前后端分离)

    原文链接:http://ued.taobao.org/blog/2014/04/full-stack-development-with-nodejs/ 随着不同终端(pad/mobile/pc)的兴起 ...

  7. 也谈基于NodeJS的全栈式开发(基于NodeJS的前后端分离)

    前言 为了解决传统Web开发模式带来的各种问题,我们进行了许多尝试,但由于前/后端的物理鸿沟,尝试的方案都大同小异.痛定思痛,今天我们重新思考了“前后端”的定义,引入前端同学都熟悉的NodeJS,试图 ...

  8. 基于NodeJS的全栈式开发(基于NodeJS的前后端分离)

    也谈基于NodeJS的全栈式开发(基于NodeJS的前后端分离) 前言 为了解决传统Web开发模式带来的各种问题,我们进行了许多尝试,但由于前/后端的物理鸿沟,尝试的方案都大同小异.痛定思痛,今天我们 ...

  9. 优秀开源平台,前后端分离快速开发平台,一站式多端开发(PC+APP)

    JNPF平台架构介绍 JNPF快速开发平台采用前后端分离技术.采用B/S架构开发,形成一站式开发多端(APP+PC)使用. PC端版本介绍 第一个当然是当下热门的.net core了,运行环境为Vis ...

随机推荐

  1. 【技术博客】忘记密码界面的Vue前端实现

    一.基本流程 [登录界面] --> [点击忘记密码] --> [输入个人邮箱和验证码] --> [系统发送邮箱验证] --> [用户在限定时间内登录邮箱,查收验证码] --&g ...

  2. Docker——Registry 通过Shell管理私有仓库镜像

    使用方法: 复制代码保存为 image_registry.sh sh image_registry.sh  -h   #查看帮助 HUB=10.0.29.104:5000 改为自己的地址 #!/bin ...

  3. Linux_rpm包管理

    一.rpm包命令规范 1.包的组成 主包:bind-9.7.1-1.el5.i586.rpm 子包:bind-libs-9.7.1-1.el5.i586.rpm bind-utils-9.7.1-1. ...

  4. Deploying Portainer CE in Docker

    Portainer是一个轻量级的管理UI,它允许你轻松地管理你的Docker和Kubernetes集群 https://documentation.portainer.io/v2.0/deploy/c ...

  5. UEFI和Legacy兼容启动U盘制作

    应用场景 自己有一个可启动移动硬盘,是属于老式的BIOS启动方式,最近换了新电脑,因为电脑只支持uefi的启动方式,所以决心为移动硬盘增加uefi启动支持,如何将一个只支持BIOS启动(或者 Lega ...

  6. Linux :忘记使用nohup该如何补救

    Linux :忘记使用nohup该如何补救 目录 Linux :忘记使用nohup该如何补救 0x00 摘要 0x01 问题描述 1.1 为何关闭进程 1.2 nohup 作用 0x02 简述 2.1 ...

  7. 为鸿蒙OS说两句公道话(我对鸿蒙OS的一些看法)

    为鸿蒙说两句公道话 今天看了鸿蒙系统的评测,看完后我感觉很欣慰,为什么这么说 ? 不是很多人吐槽鸿蒙是 Android 套壳吗 ?或者叫鸿蒙 UI 吗?说鸿蒙没有自己的核心技术.看了鸿蒙系统的设计,底 ...

  8. 吐血整理!Python常用第三方库,码住!!!

    ​ Python作为一种编程语言近年来越来越受欢迎,它为什么这么火? 其中一个重要原因就是因为Python的库丰富--Python语言提供超过15万个第三方库,Python库之间广泛联系.逐层封装.几 ...

  9. [leetcode] 39. 组合总和(Java)(dfs、递归、回溯)

    39. 组合总和 直接暴力思路,用dfs+回溯枚举所有可能组合情况.难点在于每个数可取无数次. 我的枚举思路是: 外层枚举答案数组的长度,即枚举解中的数字个数,从1个开始,到target/ min(c ...

  10. .Net之简单通知服务

    开篇语 这两天看见有大佬分享使用钉钉和企业微信的机器人来做通知报警,然后我想到了我使用的另一个第三方软件捷易快信(可能大家都不知道这个东西,我也忘了我最开始是咋知道的),该服务的优点是可以通过微信进行 ...