文章目录

  • 一:消息推送常用方式介绍
    • 1.1 轮询:浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实时返回数据给浏览器
    • 1.2 长轮询:浏览器发出ajax请求,服务器端接收到请求后,会阻塞请求直到有数据或者超时才返回
    • 1.3 SSE(server-sent event):服务器发送事件
    • 1.4 websocket:是一种在基于TCP连接上进行全双工通信的协议
  • 二:websocket介绍
    • 2.1 什么是websocket
    • 2.2 websocket的原理
    • 2.3 websocket与http的关系
      • 2.3.1 相同点
      • 2.3.2 不同点
      • 2.3.3 联系
      • 2.3.4 总体过程
    • 2.4 说明
    • 2.5 原理解析
      • 2.5.1 请求数据
      • 2.5.2 响应数据
  • 三:websocket解决的问题
    • 3.1 http存在的问题
    • 3.2 long poll(长轮询)
    • 3.3 Ajax轮询
    • 3.4 websocket的改进
  • 四:websocket API
    • 4.1 客户端【浏览器】API
      • 4.1.1 websocket对象创建
      • 4.1.2 websocket对象相关事件
      • 4.1.3 websocket对象提供的方法
      • 4.1.4 代码演示
    • 4.2 服务端 API
      • 4.2.1 Endpoint说明
      • 4.2.2 定义Endpoint
      • 4.2.3 生命周期方法
      • 4.2.4 服务端如何接收客户端发送的数据
      • 4.2.5 服务端如何推送数据给客户端
  • 五:在线聊天室代码实现
    • 5.1 流程分析
    • 5.2 引入websocket依赖
    • 5.2 前端登录界面
    • 5.2 前端聊天界面
    • 5.3 发送消息实体类
      • 5.3.1 客户端 --> 服务端
      • 5.3.2 服务端 --> 客户端
    • 5.4 编写websocket配置类
    • 5.5 登录以及页面跳转
    • 5.6 后台核心代码

一:消息推送常用方式介绍

1.1 轮询:浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实时返回数据给浏览器

1.2 长轮询:浏览器发出ajax请求,服务器端接收到请求后,会阻塞请求直到有数据或者超时才返回

1.3 SSE(server-sent event):服务器发送事件

  • SSE在服务器和客户端之间打开一个单向通道
  • 服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息
  • 服务器有数据变更时将数据流式传输到客户端

1.4 websocket:是一种在基于TCP连接上进行全双工通信的协议

二:websocket介绍

2.1 什么是websocket

  1. WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  2. 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  3. Websocket是一个持久化的协议

2.2 websocket的原理

  1. websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
  2. 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
  3. websocket是一种全新的协议,不属于http无状态协议,协议名为"ws"

2.3 websocket与http的关系

2.3.1 相同点

  • 都是基于tcp的,都是可靠性传输协议
  • 都是应用层协议

2.3.2 不同点

  • WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息
  • HTTP是单向的
  • WebSocket是需要浏览器和服务器握手进行建立连接的
  • 而http是浏览器发起向服务器的连接,服务器预先并不知道这个连接

2.3.3 联系

  • WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的

2.3.4 总体过程

  • 首先,客户端发起http请求,经过3次握手后,建立起TCP连接;http请求里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等;
  • 然后,服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据;
  • 最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。

2.4 说明

全双工(Full Duplex):允许数据在两个方向上同时传输。
半双工(Half Duplex):允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上传输。

2.5 原理解析

2.5.1 请求数据

GET ws://localhost/chat HTTP/1.1
Host: localhost
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Extensions: permessage-deflate

2.5.2 响应数据

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Extensions: permessage-deflate

三:websocket解决的问题

3.1 http存在的问题

  • http是一种无状态协议,每当一次会话完成后,服务端都不知道下一次的客户端是谁,需要每次知道对方是谁,才进行相应的响应,因此本身对于实时通讯就是一种极大的障碍
  • http协议采用一次请求,一次响应,每次请求和响应就携带有大量的header头,对于实时通讯来说,解析请求头也是需要一定的时间,因此,效率也更低下
  • 最重要的是,需要客户端主动发,服务端被动发,也就是一次请求,一次响应,不能实现主动发送

3.2 long poll(长轮询)

  • 对于以上情况就出现了http解决的第一个方法——长轮询
  • 基于http的特性,简单点说,就是客户端发起长轮询,如果服务端的数据没有发生变更,会 hold 住请求,直到服务端的数据发生变化,或者等待一定时间超时才会返回。返回后,客户端又会立即再次发起下一次长轮询
  • 优点是解决了http不能实时更新的弊端,因为这个时间很短,发起请求即处理请求返回响应,实现了“伪·长连接”
  • 张三取快递的例子,张三今天一定要取到快递,他就一直站在快递点,等待快递一到,立马取走
  • 推送延迟。服务端数据发生变更后,长轮询结束,立刻返回响应给客户端。
  • 服务端压力。长轮询的间隔期一般很长,例如 30s、60s,并且服务端 hold 住连接不会消耗太多服务端资源。

3.3 Ajax轮询

特点:

  • 基于http的特性,简单点说,就是规定每隔一段时间就由客户端发起一次请求,查询有没有新消息,如果有,就返回,如果没有等待相同的时间间隔再次询问
  • 优点是解决了http不能实时更新的弊端,因为这个时间很短,发起请求即处理请求返回响应,把这个过程放大n倍,本质上还是request = response
  • 举个形象的例子(假设张三今天有个快递快到了,但是张三忍耐不住,就每隔十分钟给快递员或者快递站打电话,询问快递到了没,每次快递员就说还没到,等到下午张三的快递到了,but,快递员不知道哪个电话是张三的,(可不是只有张三打电话,还有李四,王五),所以只能等张三打电话,才能通知他,你的快递到了)

案例分析:
从例子上来看有两个问题:

  • 假如说,张三打电话的时间间隔为10分钟,当他收到快递前最后一次打电话,快递员说没到,他刚挂掉电话,快递入库了(就是到了),那么等下一次时间到了,张三打电话知道快递到了,那么这样的通讯算不算实时通讯?很显然,不算,中间有十分钟的时间差,还不算给快递员打电话的等待时间(抽象的解释:每次request的请求时间间隔等同于十分钟,请求解析相当于等待)
  • 假如说张三所在的小区每天要收很多快递,每个人都采取主动给快递员打电话的方式,那么快递员需要以多快的速度接到,其他人打电话占线也是问题(抽象解释:请求过多,服务端响应也会变慢)

Ajax轮询存在的问题:

  • 推送延迟。
  • 服务端压力。配置一般不会发生变化,频繁的轮询会给服务端造成很大的压力。
  • 推送延迟和服务端压力无法中和。降低轮询的间隔,延迟降低,压力增加;增加轮询的间隔,压力降低,延迟增高

3.4 websocket的改进

一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。在客户端断开WebSocket连接或Server端中断连接前,不需要客户端和服务端重新发起连接请求。在海量并发及客户端与服务器交互负载流量大的情况下,极大的节省了网络带宽资源的消耗,有明显的性能优势,且客户端发送和接受消息是在同一个持久连接上发起,实现了“真·长链接”,实时性优势明显。

WebSocket有以下特点:

  • 是真正的全双工方式,建立连接后客户端与服务器端是完全平等的,可以互相主动请求。而HTTP长连接基于HTTP,是传统的客户端对服务器发起请求的模式。
  • HTTP长连接中,每次数据交换除了真正的数据部分外,服务器和客户端还要大量交换HTTP header,信息交换效率很低。Websocket协议通过第一个request建立了TCP连接之后,之后交换的数据都不需要发送 HTTP header就能交换数据,这显然和原有的HTTP协议有区别所以它需要对服务器和客户端都进行升级才能实现(主流浏览器都已支持HTML5)

四:websocket API

4.1 客户端【浏览器】API

4.1.1 websocket对象创建

let  ws  =  new WebSocket(URL);

url说明:
格式:协议://ip地址/访问路径
协议:协议名称为 ws

4.1.2 websocket对象相关事件

4.1.3 websocket对象提供的方法

4.1.4 代码演示

4.2 服务端 API

4.2.1 Endpoint说明

  • Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范。
  • Java WebSocket应用由一系列的Endpoint组成。Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口。

4.2.2 定义Endpoint

  • 第一种是编程式, 即继承类 javax.websocket.Endpoint并实现其方法。
  • 第二种是注解式, 即定义一个POJO, 并添加 @ServerEndpoint相关注解。

4.2.3 生命周期方法

4.2.4 服务端如何接收客户端发送的数据

编程式:通过添加 MessageHandler 消息处理器来接收消息
注解式:在定义Endpoint时,通过@OnMessage注解指定接收消息的方法

4.2.5 服务端如何推送数据给客户端

  • 发送消息则由 RemoteEndpoint 完成, 其实例由 Session 维护。
  • 发送消息有2种方式发送消息
    通过session.getBasicRemote 获取同步消息发送的实例 , 然后调用其 sendXxx()方法发送消息
    通过session.getAsyncRemote 获取异步消息发送实例,然后调用其 sendXxx() 方法发送消息

五:在线聊天室代码实现

5.1 流程分析

5.2 引入websocket依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

5.2 前端登录界面

<!DOCTYPE html>
<html lang="en"><head><title>随意时光-登录</title><meta name="viewport" content="width=device-width, initial-scale=1"/><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><meta name="keywords"content="Transparent Sign In Form Responsive Widget,Login form widgets, Sign up Web forms , Login signup Responsive web form,Flat Pricing table,Flat Drop downs,Registration Forms,News letter Forms,Elements"/><script type="application/x-javascript">addEventListener("load", function () {setTimeout(hideURLbar, 0);}, false);function hideURLbar() {window.scrollTo(0, 1);}</script><script src="/js/jquery.min.js"></script><link rel="icon" href="/img/chat.ico" type="image/x-icon"/><link rel="stylesheet" href="/css/font-awesome.css"/> <!-- Font-Awesome-Icons-CSS --><link rel="stylesheet" href="/css/login.css" type="text/css" media="all"/> <!-- Style-CSS -->
</head>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="/js/vue.js"></script>
<script src="/js/axios-0.18.0.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script><body class="background"><div class="header-w3l"><h1>WeChat</h1>
</div><div class="main-content-agile" id="app"><div class="sub-main-w3"><h5>账号登录</h5><el-form id="loginForm" size="medium"><el-row><el-col :span="24"><el-form-item class="icon1" label="账号:" prop="account"><el-input placeholder="用户名/手机号/邮箱" id="account" style="width: 80%;" v-model="user.account" type="text"></el-input></el-form-item></el-col></el-row><el-row><el-col :span="24"><el-form-item class="icon2" label="密码:" prop="password"><el-input placeholder="密码" id="password" style="width: 80%;" v-model="user.password" type="password"></el-input></el-form-item></el-col></el-row></el-form><div class="clear"></div><input type="button" id="btn1" @click="login" value="登录"/><div class="icon1"><span id="err_msg" style="color: red; ">{{errMessage}}</span></div></div>
</div>
<div class="footer"><p style="color: #1E1E1E;font-weight: bold">随意石光科技有限公司 版权所有、盗版必究 </p>
</div></body>
<script>new Vue({el:"#app",data() {return {errMessage: "",user:{account:"",password:""},}},methods: {login() {let self = this;axios.post("/userLogin",this.user).then(res => {//判断登陆是否成功if(res.data.code === 200) {location.href = "websocket/main.html";} else {self.$message.error(res.data.msg);}});},}});
</script>
</html>

5.2 前端聊天界面

<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="format-detection" content="telephone=no"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta name="viewport"content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0"><meta name="mobile-web-app-capable" content="yes"><meta name="apple-mobile-web-app-capable" content="yes"><meta content="yes" name="apple-mobile-web-app-capable"><meta content="yes" name="apple-touch-fullscreen"><meta name="full-screen" content="yes"><meta content="default" name="apple-mobile-web-app-status-bar-style"><meta name="screen-orientation" content="portrait"><meta name="browsermode" content="application"><meta name="msapplication-tap-highlight" content="no"><meta name="x5-orientation" content="portrait"><meta name="x5-fullscreen" content="true"><meta name="x5-page-mode" content="app"><base target="_blank"><title>WeChat-聊天室</title><link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css"/><link rel="stylesheet" href="/css/chat.css">
</head><body>
<img style="width:100%;height:100%" src="/img/dlam.jpg"><div class="abs cover contaniner" id="app"><div class="abs cover pnl"><div class="top pnl-head" style="padding: 20px ; color: white;"><div id="userName">用户:{{username}}<span style='float: right;color: green' v-if="isOnline">在线</span><span style='float: right;color: red' v-else>离线</span></div><div id="chatMes" v-show="chatMes" style="text-align: center;color: #6fbdf3;font-family: 新宋体">正在和 <font face="楷体">{{toName}}</font> 聊天</div></div><!--聊天区开始--><div class="abs cover pnl-body" id="pnlBody"><div class="abs cover pnl-left" id="initBackground" style="background-color: white; width: 100%"><div class="abs cover pnl-left" id="chatArea" v-show="isShowChat"><div class="abs cover pnl-msgs scroll" id="show"><div class="pnl-list" id="hists"><!-- 历史消息 --></div><div class="pnl-list" id="msgs" v-for="message in historyMessage"><!-- 消息这展示区域 --><div class="msg guest" v-if="message.toName"><div class="msg-right"><div class="msg-host headDefault"></div><div class="msg-ball">{{message.message}}</div></div></div><div class="msg robot" v-else><div class="msg-left" worker=""><div class="msg-host photo"style="background-image: url(/img/avatar/Member002.jpg)"></div><div class="msg-ball">{{message.message}}</div></div></div></div></div><div class="abs bottom pnl-text"><div class="abs cover pnl-input"><textarea class="scroll" id="context_text" @keyup.enter="submit" wrap="hard" placeholder="在此输入文字信息..."v-model="sendMessage.message"></textarea><div class="abs atcom-pnl scroll hide" id="atcomPnl"><ul class="atcom" id="atcom"></ul></div></div><div class="abs br pnl-btn" id="submit" @click="submit"style="background-color: rgb(32, 196, 202); color: rgb(255, 255, 255);">发送</div><div class="pnl-support" id="copyright"><a href="http://www.itcast.cn">随意石光,版本所有</a></div></div></div><!--聊天区 结束--><div class="abs right pnl-right"><div class="slider-container hide"></div><div class="pnl-right-content"><div class="pnl-tabs"><div class="tab-btn active" id="hot-tab">好友列表</div></div><div class="pnl-hot"><ul class="rel-list unselect"><li class="rel-item" v-for="friend in friendsList"><a @click='showChat(friend)'>{{friend}}</a></li></ul></div></div><div class="pnl-right-content"><div class="pnl-tabs"><div class="tab-btn active">系统广播</div></div><div class="pnl-hot"><ul class="rel-list unselect" id="broadcastList"><li class="rel-item" style="color: #9d9d9d;font-family: 宋体" v-for="name in systemMessages">您的好友{{name}} 已上线</li></ul></div></div></div></div></div></div>
</div>
<script src="/js/vue.js"></script>
<script src="/js/axios-0.18.0.js"></script>
<script>let ws;new Vue({el: "#app",data() {return {isShowChat: false,chatMes: false,isOnline: true,username:"",toName: "",sendMessage: {toName: "",message: ""},inputMessage: "",historyMessage: [],friendsList: [],systemMessages : []}},created() {this.init();},methods: {async init() {await axios.get("/websocket/user/getUsername").then(res => {this.username = res.data;});//创建webSocket对象//ws = new WebSocket("ws://47.97.50.125:8989/chat");ws = new WebSocket("ws://127.0.0.1:8989/chat");//给ws绑定事件ws.onopen = this.onopen;//接收到服务端推送的消息后触发ws.onmessage = this.onMessage;ws.onclose = this.onClose;},showChat(name) {this.toName = name;//清除聊天区的数据let history = sessionStorage.getItem(this.toName);if (!history) {this.historyMessage = [];} else {this.historyMessage = JSON.parse(history);}//展示聊天对话框this.isShowChat = true;//显示“正在和谁聊天”this.chatMes = true;},submit() {this.sendMessage.toName = this.toName;this.historyMessage.push(JSON.parse(JSON.stringify(this.sendMessage)));sessionStorage.setItem(this.toName, JSON.stringify(this.historyMessage));ws.send(JSON.stringify(this.sendMessage));this.sendMessage.message = "";},onOpen() {this.isOnline = true;},onClose() {this.isOnline = false;},onMessage(evt) {//获取服务端推送过来的消息var dataStr = evt.data;//将dataStr 转换为json对象var res = JSON.parse(dataStr);//判断是否是系统消息if(res.system) {//系统消息  好友列表展示let names = res.message;this.friendsList = [];this.systemMessages = [];for (let i = 0; i < names.length; i++) {if(names[i] != this.username) {this.friendsList.push(names[i]);this.systemMessages.push(names[i]);}}}else {//非系统消息let history = sessionStorage.getItem(res.fromName);if (!history) {this.historyMessage = [res];} else {this.historyMessage.push(res);}sessionStorage.setItem(res.fromName, JSON.stringify(this.historyMessage));}}}});</script>
</body>
</html>

5.3 发送消息实体类

5.3.1 客户端 --> 服务端

{“toName”:“张三”,“message”:“你好”}

import lombok.Data;/*** @version v1.0* @ClassName: ClientToServerMessage* @Description: 客户端 ->服务端  {"toName":"张三","message":"hello world"}* @Author: ikun*/
@Data
public class ClientToServerMessage {/*** 发送给谁*/private String toName;/*** 发送的内容*/private String message;
}

5.3.2 服务端 --> 客户端

  • 系统消息格式:{“system”:true,“fromName”:null,“message”:[“李四”,“王五”]}
  • 推送给某一个用户的消息格式:{“system”:false,“fromName”:“张三”,“message”:“你好”}
import lombok.Data;/*** @version v1.0* @ClassName: ServerToClientMessage* @Description: 服务端 -> 浏览器*/
@Data
public class ServerToClientMessage {/*** 是否系统消息*/private boolean isSystem;/*** 需要发送给那个人,如果是系统消息,可以为空*/private String fromName;/*** 消息的内容* 如果是系统消息是数组*/private Object message;
}

5.4 编写websocket配置类

  • 扫描添加有@ServerEndpoint注解的 Bean
/*** @author ikun*/
@Configuration
public class WebSocketConfig {/***  注入ServerEndpointExporter,*   这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}}
  • 获取 HttpSession 对象
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;/*** @version v1.0* @ClassName: GetHttpSessionConfig* @Description: TODO(一句话描述该类的功能)*/
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {//获取HttpSession对象HttpSession httpSession = (HttpSession) request.getHttpSession();//将httpSession对象保存起来sec.getUserProperties().put(HttpSession.class.getName(),httpSession);}
}

5.5 登录以及页面跳转

import com.sysg.websocket.entity.Result;
import com.sysg.websocket.entity.User;
import org.springframework.web.bind.annotation.*;import javax.servlet.http.HttpSession;@RestController
@RequestMapping("/websocket/user")
public class WebsocketUserController {@RequestMapping("/main.html")public String getMainHtml(){return "websocket/main";}/*** 登录* @param user 提交的用户数据,包含用户名和密码* @param session* @return*/@PostMapping("/login")public Result login(@RequestBody User user, HttpSession session) {Result result = new Result();if(user != null && "123".equals(user.getPassword())) {result.setFlag(true);//将数据存储到session对象中session.setAttribute("user",user.getUsername());} else {result.setFlag(false);result.setMessage("登陆失败");}return result;}/*** 获取用户名* @param session* @return*/@GetMapping("/getUsername")public String getUsername(HttpSession session) {return (String) session.getAttribute("user");}
}

5.6 后台核心代码

import com.alibaba.fastjson.JSON;
import com.sysg.websocket.config.GetHttpSessionConfig;
import com.sysg.websocket.utils.MessageUtils;
import com.sysg.websocket.ws.pojo.ClientToServerMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;/*** @version v1.0* @ClassName: ChatEndpoint* @Description: TODO(一句话描述该类的功能)*/
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)
@Component
@Slf4j
public class ChatEndpoint {/***用来存储每一个客户端对象对应的ChatEndpoint对象*/private static final Map<String,Session> onlineUsers = new ConcurrentHashMap<>();/*** 声明session对象,通过该对象可以发送消息给指定用户*///private Session session;/*** httpSession存储了用户名*/private HttpSession httpSession;/*** 建立websocket连接后,被调用* @param session*/@OnOpenpublic void onOpen(Session session, EndpointConfig config) {//1,将session进行保存this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());String user = (String) this.httpSession.getAttribute("user");onlineUsers.put(user,session);//2,广播消息。需要将登陆的所有的用户推送给所有的用户String message = MessageUtils.getMessage(true,null, getFriends());broadcastAllUsers(message);}/*** 获取当前在线用户* @return*/public Set getFriends() {Set<String> set = onlineUsers.keySet();return set;}/*** 广播消息给客户端* @param message*/private void broadcastAllUsers(String message) {try {//遍历map集合Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();for (Map.Entry<String, Session> entry : entries) {//获取到所有用户对应的session对象Session session = entry.getValue();//发送消息session.getBasicRemote().sendText(message);}} catch (Exception e) {//记录日志log.error(e.getMessage());}}/*** 浏览器发送消息到服务端,该方法被调用** 张三  -->  李四* @param message*/@OnMessagepublic void onMessage(String message) {try {//将消息推送给指定的用户ClientToServerMessage msg = JSON.parseObject(message, ClientToServerMessage.class);//获取 消息接收方的用户名String toName = msg.getToName();String mess = msg.getMessage();//获取消息接收方用户对象的session对象Session session = onlineUsers.get(toName);String user = (String) this.httpSession.getAttribute("user");String msg1 = MessageUtils.getMessage(false, user, mess);session.getBasicRemote().sendText(msg1);} catch (Exception e) {//记录日志log.error(e.getMessage());}}/*** 断开 websocket 连接时被调用* @param session*/@OnClosepublic void onClose(Session session) {//1,从onlineUsers中剔除当前用户的session对象String user = (String) this.httpSession.getAttribute("user");onlineUsers.remove(user);//2,通知其他所有的用户,当前用户下线了String message = MessageUtils.getMessage(true,null,getFriends());broadcastAllUsers(message);}
}
import com.alibaba.fastjson.JSON;
import com.sysg.websocket.ws.pojo.ServerToClientMessage;/*** @version v1.0* @ClassName: MessageUtils* @Description: 封装json格式消息的工具类*/
public class MessageUtils {/*** 将数据转化为json格式的数据* @param isSystemMessage* @param fromName* @param message* @return*/public static String getMessage(boolean isSystemMessage,String fromName, Object message) {ServerToClientMessage result = new ServerToClientMessage();result.setSystem(isSystemMessage);result.setMessage(message);if(fromName != null) {result.setFromName(fromName);}return JSON.toJSONString(result);}
}

webSocket介绍及项目实战【在线聊天系统】相关推荐

  1. React全Hook项目实战在线聊天室历程(三):加个音乐直播?

    前情提要: React全Hook项目实战在线聊天室历程(一):基本功能 React全Hook项目实战在线聊天室历程(二):引用与话题功能 正文 聊天应该有什么?背景音乐,茶与酒,零食,后两个我是没法实 ...

  2. QiYuAdmin-metronic首页的js和css介绍(SpringBoot项目实战)

    简介 这篇文章主要是对metronic首页的js和css进行简单的介绍,以及针对QiYuAdmin项目梳理出一些公用的js和css,什么是共用的js和css?共用的js和css是很多页面都会用到相同的 ...

  3. 嵌入式学习项目实战 --- 在线词典

    目录 一.前言 二.项目功能 三.程序流程 1.客户端 2.服务器 四.代码实现 1.客户端代码 2.服务器代码 3.Makefile 一.前言 本文学习自[华清远见]的一个开源嵌入式项目在线词典综合 ...

  4. Xamarin.Forms 5.0 项目实战发布!

    活动介绍 本次活动主要是 .NET Xamarin.Forms 移动端项目开发实战教程, 与以往相同, 本次的收入(其它部分会另行说明) 将用于社区公益活动, 不限于: 公益性质的个人/组织机构捐赠 ...

  5. Android Compose Bloom 项目实战 (一) : 项目说明与配置

    1. 项目介绍 Bloom是谷歌 AndroidDevChallenge (Android 开发挑战赛) 中的一期活动,目的是为了推广Compose,非常适合用来练手,通过这个项目,我们可以很好的入门 ...

  6. 视频教程-项目实战:在线电影票购买系统-Java

    项目实战:在线电影票购买系统 系统分析师,项目经理,特级讲师:11年项目经验,8年教学经验:在多个大型企业级项目中担任过重要角色. 肖海鹏 ¥359.00 立即订阅 扫码下载「CSDN程序员学院APP ...

  7. ASP.NET Core分布式项目实战(业务介绍,架构设计,oAuth2,IdentityServer4)--学习笔记...

    任务4:第一章计划与目录 敏捷产品开发流程 原型预览与业务介绍 整体架构设计 API 接口设计 / swagger Identity Server 4 搭建登录 账号 API 实现 配置中心 任务5: ...

  8. ASP.NET Core分布式项目实战(课程介绍,MVP,瀑布与敏捷)--学习笔记

    任务1:课程介绍 课程目标: 1.进一步理解 ASP.NET Core 授权认证框架.MVC 管道 2.掌握 Oauth2,结合 Identity Sercer4 实现 OAuth2 和 OpenID ...

  9. 【SpringBoot项目实战+思维导图】瑞吉外卖①(项目介绍、开发环境搭建、后台登陆/退出功能开发)

    文章目录 软件开发整体介绍 软件开发流程 角色分工 软件环境 瑞吉外卖项目介绍 项目介绍 产品原型 技术选型 功能架构 角色 开发环境搭建 数据库环境搭建 创建数据库 数据库表导入 数据库表介绍 Ma ...

最新文章

  1. Spark 1.6发布:引入Dataset接口
  2. Ubuntu14.04安装pip及配置
  3. 【网络传输与RTC】
  4. 排序算法 —— 堆排序
  5. dubbo+rabbitmq+hystrix实现服务的高可用
  6. 最强云硬盘来了,让AI模型迭代从1周缩短到1天
  7. 去百度/阿里/腾讯…做测试的,都是什么样的人?
  8. OpenShift 4 - 对镜像进行合规扫描,加固应用镜像安全
  9. unwrap函数c语言实现,AppDomain与Assembly的动态加载与卸载代码详解
  10. 经典算法书籍推荐(亲测有效)
  11. plc编程语言有几种?plc常用的编程语言
  12. 浅谈如何带领好一个团队
  13. killall: command not found
  14. 团队管理之—— 定目标:让你的方向与公司的方向保持一致
  15. pyinstxtractor 源码分析及填坑
  16. 【ELT.ZIP】OpenHarmony啃论文俱乐部——点燃主缓存压缩技术火花
  17. 数据库题目之并发控制
  18. 小议和浅谈 《龙族》的经典设计(一)
  19. C#窗体Combobox简单用法(1)
  20. [转]日历生成算法-中国公历(格里历)

热门文章

  1. ofstream的使用方法(转)
  2. html单选按钮字段颜色设置,如何更改单选按钮的颜色?
  3. linux 版本命令
  4. java printwriter用法_Java中printwriter类的用法 | 学步园
  5. 二叉搜索树(BST)分析及实现
  6. 计算机信息安全 心得,信息安全心得体会精选.doc
  7. 喜大普奔,苹果可以推送退款通知了
  8. 什么是分布式锁,分布式锁有什么作用?
  9. Ubuntu安装genymotion
  10. 超级安卓模拟器genymotion