# 利刃出鞘_Tomcat 核心原理解析(十一)-- Tomcat 附加功能 WebSocket -- 3
利刃出鞘_Tomcat 核心原理解析(十一)-- Tomcat 附加功能 WebSocket – 3
一、Tomcat专题 - WebSocket - 案例 - OnMessage分析
1、WebSocket DEMO 案例 实现流程分析:OnMessage 分析
2、在项目 dzs168_chat_room 中,在 websocket 类 ChatSocket.java 中,
创建 public void onMessage(String message, Session session) {…} 方法。分析流程。
//{"fromName":"Deng","toName":"HEIMA","content":"约会呀"}
@OnMessage
public void onMessage(String message, Session session) {
//1. 获取客户端的信息内容, 并解析
//2. 判定是否有接收人
//3. 如果接收人是否是广播(all), 如果是, 则说明发送广播消息
//4. 不是all , 则给指定的用户推送消息
}
二、Tomcat专题 - WebSocket - 案例 - OnMessage功能实现
1、在项目 dzs168_chat_room 中,在 websocket 类 ChatSocket.java 中,
完成 onMessage(String message, Session session) {…} 方法代码编写。
//{"fromName":"Deng","toName":"HEIMA","content":"约会呀"}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
System.out.println("onMessage : name = " + httpSession.getAttribute("username")+ ", message=" + message );
//1. 获取客户端的信息内容, 并解析
Map<String,String> messageMap = JSON.parseObject(message, Map.class);
String fromName = messageMap.get("fromName");
String toName = messageMap.get("toName");
String content = messageMap.get("content");
//2. 判定是否有接收人
if(toName == null || toName.isEmpty()){
return;
}
//3. 如果接收人是否是广播(all), 如果是, 则说明发送广播消息
String messageContent = MessageUtil.getContent(MessageUtil.TYPE_MESSAGE, fromName, toName, content);
System.out.println("服务端给客户端发送消息, 消息内容: " + messageContent);
if("all".equals(toName)){
//3.1 组装消息内容
broadcastAllUsers(messageContent);
}else{//4. 不是all , 则给指定的用户推送消息
singlePushMessage(messageContent, fromName,toName);
}
}
2、在项目 dzs168_chat_room 中,在 websocket 类 ChatSocket.java 中,
创建 //给指定用户推送消息 private void singlePushMessage(String content, String fromName, String toName) throws IOException {…} 方法,并完成代码编写。
//给指定用户推送消息
private void singlePushMessage(String content, String fromName, String toName) throws IOException {
boolean isOnline = false;
//1. 判定当然接收人是否在线
for (HttpSession hsession : onlineUsers.keySet()) {
if(toName.equals(hsession.getAttribute("username"))){
isOnline = true;
}
}
//2. 如果存在, 发送消息
if(isOnline){
for (HttpSession hsession : onlineUsers.keySet()) {
if (hsession.getAttribute("username").equals(fromName) || hsession.getAttribute("username").equals(toName)){
onlineUsers.get(hsession).session.getBasicRemote().sendText(content);
}
}
}
}
3、项目 dzs168_chat_room 中,websocket 类 ChatSocket.java 代码。
/**
* project_tomcat\dzs168_chat_room\src\djh\it\websocket\ChatSocket.java
*
* 2024-9-2 创建 websocket 类 ChatSocket.java
*/
package djh.it.websocket;
import djh.it.utils.MessageUtil;
import com.alibaba.fastjson.JSON;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@ServerEndpoint(value = "/websocket",configurator = GetHttpSessionConfigurator.class )
public class ChatSocket {
private Session session;
private HttpSession httpSession;
//保存当前系统中登录的用户的HttpSession信息, 及对应的Endpoint实例信息
private static Map<HttpSession , ChatSocket> onlineUsers = new HashMap<HttpSession, ChatSocket>();
private static int onlineCount = 0;
@OnOpen
public void onOpen(Session session, EndpointConfig config){
//1. 记录webSocket的会话信息对象Session
this.session = session;
//2. 获取当前登录用户HttpSession信息.
HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
this.httpSession = httpSession;
System.out.println("当前登录用户 : " + httpSession.getAttribute("username") +", Endpoint : " +hashCode());
//3. 记录当前登录用户信息, 及对应的Endpoint实例
if (httpSession.getAttribute("username") != null){
onlineUsers.put(httpSession,this);
}
//4. 获取当前所有登录用户 --------> DZS168,dzs,TOM...
String names = getNames();
//5. 组装消息 ---> {"data":"dzs168,Deng,study","toName":"","fromName":"","type":"user"}
String message = MessageUtil.getContent(MessageUtil.TYPE_USER, "", "", names);
//6. 通过广播的形式发送消息
//session.getBasicRemote().sendText("");
broadcastAllUsers(message);
//7. 记录当前用户登录数 .
incrCount();
}
//{"fromName":"Deng","toName":"HEIMA","content":"约会呀"}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
System.out.println("onMessage : name = " + httpSession.getAttribute("username")+ ", message=" + message );
//1. 获取客户端的信息内容, 并解析
Map<String,String> messageMap = JSON.parseObject(message, Map.class);
String fromName = messageMap.get("fromName");
String toName = messageMap.get("toName");
String content = messageMap.get("content");
//2. 判定是否有接收人
if(toName == null || toName.isEmpty()){
return;
}
//3. 如果接收人是否是广播(all), 如果是, 则说明发送广播消息
String messageContent = MessageUtil.getContent(MessageUtil.TYPE_MESSAGE, fromName, toName, content);
System.out.println("服务端给客户端发送消息, 消息内容: " + messageContent);
if("all".equals(toName)){
//3.1 组装消息内容
broadcastAllUsers(messageContent);
}else{//4. 不是all , 则给指定的用户推送消息
singlePushMessage(messageContent, fromName,toName);
}
}
//给指定用户推送消息
private void singlePushMessage(String content, String fromName, String toName) throws IOException {
boolean isOnline = false;
//1. 判定当然接收人是否在线
for (HttpSession hsession : onlineUsers.keySet()) {
if(toName.equals(hsession.getAttribute("username"))){
isOnline = true;
}
}
//2. 如果存在, 发送消息
if(isOnline){
for (HttpSession hsession : onlineUsers.keySet()) {
if (hsession.getAttribute("username").equals(fromName) || hsession.getAttribute("username").equals(toName)){
onlineUsers.get(hsession).session.getBasicRemote().sendText(content);
}
}
}
}
// 发送广播消息
private void broadcastAllUsers(String message) {
for (HttpSession hsession : onlineUsers.keySet()) {
try {
onlineUsers.get(hsession).session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//获取所有的在线用户
private String getNames() {
String names = "";
if(onlineUsers.size()>0){
for (HttpSession hsession : onlineUsers.keySet()) {
String username = (String) hsession.getAttribute("username");
names += username+",";
}
}
return names.substring(0,names.length()-1);
}
public int getOnlineCount(){
return onlineCount;
}
public synchronized void incrCount(){
onlineCount ++;
}
public synchronized void decrCount(){
onlineCount --;
}
}
三、Tomcat专题 - WebSocket - 案例 - OnMessage功能测试
1、项目 dzs168_chat_room 中,前端源码 chat.jsp 页面。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<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>段子手168-聊天室</title>
<link href="css/bootstrap.min.css" rel="stylesheet" type="text/css"/>
<link rel="stylesheet" href="css/chat.css">
<script src="js/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
<%
String name = session.getAttribute("username")+"";
%>
var self = "<%= name %>";
</script>
<script type="text/javascript" src="js/ws.js"></script>
</head>
<body onload="startWebSocket(self);">
<img style="width:100%;height:100%" src="img/chat_bg.jpg">
<div class="abs cover contaniner">
<div class="abs cover pnl">
<div class="top pnl-head" style="padding: 20px ; color: white;" id="userName"></div>
<div class="abs cover pnl-body" id="pnlBody">
<div class="abs cover pnl-left">
<div class="abs cover pnl-msgs scroll" id="show">
<div class="pnl-list" id="hists"><!-- 历史消息 --></div>
<div class="pnl-list" id="msgs">
<!-- 消息这展示区域 -->
</div>
</div>
<div class="abs bottom pnl-text">
<div class="abs cover pnl-input">
<textarea class="scroll" id="context_text" onkeydown="sendMessage(self)" wrap="hard" placeholder="在此输入文字信息..."></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" style="background-color: rgb(32, 196, 202); color: rgb(255, 255, 255);" onclick="sendMsg(self)">
发送
</div>
<div class="pnl-support" id="copyright"><a href="http://www.itcast.cn">段子手168,版本所有</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" id="userlist">
</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">
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
<!-- project_tomcat\dzs168_chat_room\web\chat.jsp -->
2、项目 dzs168_chat_room 中,前端源码 login.jsp 页面。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<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-1.9.1.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>
<body class="background">
<div class="header-w3l">
<h1>段子手168聊天室</h1>
</div>
<div class="main-content-agile">
<div class="sub-main-w3">
<h2>登录</h2>
<form>
<div class="icon1">
<input placeholder="用户名" id="username" type="text"/>
</div>
<div class="icon2">
<input placeholder="密码" id="password" type="password"/>
</div>
<div class="clear"></div>
<input type="button" value="登录" onclick="login()"/>
</form>
</div>
</div>
<div class="footer">
<p>段子手168 版权所有Copyright 2024-9-1 All Rights Reserved </p>
</div>
</body>
<script type="text/javascript">
function login() {
$.ajax({
type: 'POST',
url: '/login',
dataType: 'json',
data: {
username: $("#username").val(),
password: $("#password").val()
},
success: function (data) {
if (data.success) {
window.location.href = "chat.jsp";
} else {
alert(data.message);
}
}
});
}
</script>
</html>
<!-- project_tomcat\dzs168_chat_room\web\login.jsp -->
3、项目 dzs168_chat_room 中,//定义全局的webSocket对象 ws.js 源码。
…\project_tomcat\dzs168_chat_room\web\js\ws.js
//定义全局的webSocket对象
var ws = null;
function startWebSocket(self) {
//构建WebSocket对象
if ('WebSocket' in window) {
ws = new WebSocket("ws://localhost:8080/websocket");
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket("ws://localhost:8080/websocket");
} else {
alert("not support");
}
//监听消息, 有消息传递, 会触发此方法
ws.onmessage = function (evt) {
var _data = evt.data;
console.log(">> : " + _data);
var o = JSON.parse(_data);
if (o.type == 'message') { //如果后端, 响应的是消息, 在页面展示
setMessageInnerHTML(o, self);
} else if (o.type == 'user') { // 如果服务端响应的是用户列表, 在界面展示用户列表
var userArry = o.data.split(',');
$("#userlist").empty();
$("#userlist").append('<li class="rel-item"><input type="radio" name="toUser" value="all">广播</input></li>');
$.each(userArry, function (n, value) {
if (value != self && value != 'admin') {
$("#userlist").append('<li class="rel-item"><input type="radio" name="toUser" value="'+value+'">'+value+'</input></li>');
$("#broadcastList").append('<li class="rel-item">您的好友 '+value+' 已上线</li>');
}
});
}
};
//关闭链接时触发
ws.onclose = function (evt) {
$('#userName').html("用户: "+ self +"<span style='float: right;color: red'>离线</span>");
};
//打开时触发
ws.onopen = function (evt) {
$('#userName').html("用户: "+ self +"<span style='float: right;color: green'>在线</span>");
};
}
function setMessageInnerHTML(msg, self) {
//根据后台响应的数据, 判定是展示在左侧还是右侧.
var str = "";
if(msg.toName == 'all'){
$("#broadcastList").append('<li class="rel-item"> 系统广播消息: '+msg.data+' </li>');
}else if (msg.fromName == self) {
str = "<div class=\"msg guest\"><div class=\"msg-right\"><div class=\"msg-host headDefault\"></div><div class=\"msg-ball\" title=\"今天 17:52:06\">" + msg.data + "</div></div></div>"
} else if(msg.toName == self){
str = "<div class=\"msg robot\"><div class=\"msg-left\" worker=\"" + msg.fromName + "\"><div class=\"msg-host photo\" style=\"background-image: url(../img/avatar/Member002.jpg)\"></div><div class=\"msg-ball\" title=\"今天 17:52:06\">" + msg.data + "</div></div></div>";
}
//获取到现有的内容, 追加新的消息内容
var msgs = document.getElementById("msgs");
msgs.innerHTML = msgs.innerHTML + str;
//判定消息来源的用户 , 勾选对应的好友信息
var a = $('input[name="toUser"]');
for(var i=0 ; i < a.length ; i++){
if(a[i].value == msg.fromName){
console.log(a[i]);
a[i].checked='checked';
}
}
}
// 组装消息, 发送消息
function sendMsg(self) {
var content = $("#context_text").val();
if(!content){
alert('请输入消息内容');
return ;
}
var message = {};
message.fromName = self;
message.toName = $('input:radio:checked').val(); //根据界面勾选的用户, 来决定消息发送给谁
message.content = content; //获取输入文本框中输入的内容
var msg = JSON.stringify(message);
console.log(" msg: "+msg);
ws.send(msg);//发送消息
$("#context_text").val(''); //将输入框内容置为空
}
function sendMessage(self){
if(event.keyCode == 13){
sendMsg(self);
}
}
4、运行 tomcat 服务,进行测试。
5、多个 浏览器地址栏输入:localhost:8080/ 登录几个不同用户,进行测试。
四、Tomcat专题 - WebSocket - 案例 - OnClose及OnError介绍
1、在项目 dzs168_chat_room 中,在 websocket 类 ChatSocket.java 中,
创建 public void onClose(Session session, CloseReason closeReason){…} 方法。
@OnClose
public void onClose(Session session, CloseReason closeReason){
decrCount();
System.out.println("客户端关闭了一个连接 , 当前在线人数 : " + getOnlineCount());
}
2、在项目 dzs168_chat_room 中,在 websocket 类 ChatSocket.java 中,
创建 public void onError(Session session, Throwable throwable){…} 方法。
@OnError
public void onError(Session session, Throwable throwable){
throwable.printStackTrace();
System.out.println("服务异常");
}
3、项目 dzs168_chat_room 中,websocket 类 ChatSocket.java 代码。
/**
* project_tomcat\dzs168_chat_room\src\djh\it\websocket\ChatSocket.java
*
* 2024-9-2 创建 websocket 类 ChatSocket.java
*/
package djh.it.websocket;
import djh.it.utils.MessageUtil;
import com.alibaba.fastjson.JSON;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@ServerEndpoint(value = "/websocket",configurator = GetHttpSessionConfigurator.class )
public class ChatSocket {
private Session session;
private HttpSession httpSession;
//保存当前系统中登录的用户的HttpSession信息, 及对应的Endpoint实例信息
private static Map<HttpSession , ChatSocket> onlineUsers = new HashMap<HttpSession, ChatSocket>();
private static int onlineCount = 0;
@OnOpen
public void onOpen(Session session, EndpointConfig config){
//1. 记录webSocket的会话信息对象Session
this.session = session;
//2. 获取当前登录用户HttpSession信息.
HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
this.httpSession = httpSession;
System.out.println("当前登录用户 : " + httpSession.getAttribute("username") +", Endpoint : " +hashCode());
//3. 记录当前登录用户信息, 及对应的Endpoint实例
if (httpSession.getAttribute("username") != null){
onlineUsers.put(httpSession,this);
}
//4. 获取当前所有登录用户 --------> DZS168,dzs,TOM...
String names = getNames();
//5. 组装消息 ---> {"data":"dzs168,Deng,study","toName":"","fromName":"","type":"user"}
String message = MessageUtil.getContent(MessageUtil.TYPE_USER, "", "", names);
//6. 通过广播的形式发送消息
//session.getBasicRemote().sendText("");
broadcastAllUsers(message);
//7. 记录当前用户登录数 .
incrCount();
}
//{"fromName":"Deng","toName":"HEIMA","content":"约会呀"}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
System.out.println("onMessage : name = " + httpSession.getAttribute("username")+ ", message=" + message );
//1. 获取客户端的信息内容, 并解析
Map<String,String> messageMap = JSON.parseObject(message, Map.class);
String fromName = messageMap.get("fromName");
String toName = messageMap.get("toName");
String content = messageMap.get("content");
//2. 判定是否有接收人
if(toName == null || toName.isEmpty()){
return;
}
//3. 如果接收人是否是广播(all), 如果是, 则说明发送广播消息
String messageContent = MessageUtil.getContent(MessageUtil.TYPE_MESSAGE, fromName, toName, content);
System.out.println("服务端给客户端发送消息, 消息内容: " + messageContent);
if("all".equals(toName)){
//3.1 组装消息内容
broadcastAllUsers(messageContent);
}else{//4. 不是all , 则给指定的用户推送消息
singlePushMessage(messageContent, fromName,toName);
}
}
//给指定用户推送消息
private void singlePushMessage(String content, String fromName, String toName) throws IOException {
boolean isOnline = false;
//1. 判定当然接收人是否在线
for (HttpSession hsession : onlineUsers.keySet()) {
if(toName.equals(hsession.getAttribute("username"))){
isOnline = true;
}
}
//2. 如果存在, 发送消息
if(isOnline){
for (HttpSession hsession : onlineUsers.keySet()) {
if (hsession.getAttribute("username").equals(fromName) || hsession.getAttribute("username").equals(toName)){
onlineUsers.get(hsession).session.getBasicRemote().sendText(content);
}
}
}
}
// 发送广播消息
private void broadcastAllUsers(String message) {
for (HttpSession hsession : onlineUsers.keySet()) {
try {
onlineUsers.get(hsession).session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//获取所有的在线用户
private String getNames() {
String names = "";
if(onlineUsers.size()>0){
for (HttpSession hsession : onlineUsers.keySet()) {
String username = (String) hsession.getAttribute("username");
names += username+",";
}
}
return names.substring(0,names.length()-1);
}
@OnClose
public void onClose(Session session, CloseReason closeReason){
decrCount();
System.out.println("客户端关闭了一个连接 , 当前在线人数 : " + getOnlineCount());
}
@OnError
public void onError(Session session, Throwable throwable){
throwable.printStackTrace();
System.out.println("服务异常");
}
public int getOnlineCount(){
return onlineCount;
}
public synchronized void incrCount(){
onlineCount ++;
}
public synchronized void decrCount(){
onlineCount --;
}
}
4、重新运行 tomcat 服务,多个 浏览器地址栏输入:localhost:8080/ 登录几个不同用户,再退出登录,进行测试。
上一节关联链接请点击
# 利刃出鞘_Tomcat 核心原理解析(十一)-- Tomcat 附加功能-- WebSocket – 1
# 利刃出鞘_Tomcat 核心原理解析(十一)-- Tomcat 附加功能-- WebSocket – 2