文章目录
- 一:消息推送常用方式介绍
- 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
- WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
- 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
- Websocket是一个持久化的协议
2.2 websocket的原理
- websocket约定了一个通信的规范,通过一个握手的机制,客户端和服务器之间能建立一个类似tcp的连接,从而方便它们之间的通信
- 在websocket出现之前,web交互一般是基于http协议的短连接或者长连接
- 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
*/
@Bean
public 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 {
@Override
public 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
*/
@OnOpen
public 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
*/
@OnMessage
public 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
*/
@OnClose
public 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);
}
}