WebSocket 实现 简易在线聊天室

项目重点

  • WebSocket的使用

    • @ServerEndpoint

    • 该注解标注一个WebSocket服务器,该注解有几个注意点:

      • value 表示能够使用 该 WebSocket 的 url.
      • enconders 表示在写入对象进行的编码方式(向 session对象写入数据最终还是需要转换为 String)
    • @OnOpen 参数列表为: Session

      • 在存在新连接时触发该信号
    • @OnClose 无参数列表

      • 连接被关闭时触发信息
    • @OnMessage 参数列表为 String , Session

      • 有消息写入到 WebSocket
    • 心跳检测保持与后台连接

      • 前台设置定时器来与后台交互保持连接。
  • 项目实现

    • 配置信息被省略
    • MsgWebSocket.java
package cn.tblack.ssm.socket;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.springframework.web.socket.server.standard.SpringConfigurator;

/**
 * <span>网页套接字。 该套接字需要被标注, value为一个url,也是能够使用该WebSocket的名字 </span>
 * 
 * @author TD唐登
 * @Date:2019年9月9日
 * @Version: 1.0(测试版)
 */
@ServerEndpoint(value = "/msgWebSocket", configurator = SpringConfigurator.class)
public class MsgWebSocket {

	// 记录当前在线人数
	private static long onlineCnt = 0;

	// 记录当前已经连接的次数总和
	private static long connectCnt = 0;

	// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
	// 若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
	private static ConcurrentHashMap<MsgWebSocket, String> webSocketMap = new ConcurrentHashMap<>();

	private Session session;

	@OnOpen
	/**
	 * @ 存在新连接
	 * 
	 * @param session
	 */
	public void onOpen(Session session) throws IOException {
		this.session = session;
		addOnlineCnt();
		long cnt = getConnectCnt();
		webSocketMap.put(this, "[游客" + cnt + "]");

		// 群发信息
		sendMessageToAll("---------------[ 游客  " + cnt + "]上线了!");
	}

	@OnClose
	/**
	 * @ 连接被关闭
	 */
	public void onClose() throws IOException {
		subOnlineCnt();
		String disconnect = webSocketMap.get(this);
		webSocketMap.remove(this);
		sendMessageToAll("---------------" + disconnect + "下线!");
		System.out.println("有一连接被关闭! 当前在线人数为: " + getOnlineCount());
	}

	@OnMessage
	/**
	 * @ 接收到消息
	 * 
	 * @param message
	 * @param session
	 */
	public void onMessage(String message, Session session) throws IOException {

		if("_-_-1998610_ping_-_-".equals(message)) {
			return;
		}
		// 找到发送消息的客户端
		String user = webSocketMap.get(this);

		// 群发信息
		for (Entry<MsgWebSocket, String> entry : webSocketMap.entrySet()) {

			entry.getKey().sendMessage(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy:MM:dd HH:mm:ss  "))
					+ user + ": " + message);
		}
	}

	@OnError
	/**
	 * @ 产生错误
	 * 
	 * @param session
	 * @param error
	 */
	public void onError(Session session, Throwable error) {
		System.out.println("有错误发生: " + error);
	}

	/**
	 * @ 向单个对象发送消息
	 * 
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(String message) throws IOException {

		this.session.getBasicRemote().sendText(message);

	}

	/**
	 * @ 消息群发
	 * 
	 * @param message
	 * @throws IOException
	 */
	private void sendMessageToAll(String message) throws IOException {
		// 群发信息
		for (Entry<MsgWebSocket, String> entry : webSocketMap.entrySet()) {
			entry.getKey().sendMessage(message);

		}
	}

	public static synchronized long getOnlineCount() {
		return onlineCnt;
	}

	public static synchronized long getConnectCnt() {
		return connectCnt;
	}

	public static synchronized void addOnlineCnt() {
		++connectCnt;
		++onlineCnt;
	}

	public static synchronized void subOnlineCnt() {
		--onlineCnt;
	}

	/**
	 * @ 拿到当前在线人的名字
	 * @return
	 */
	public static ArrayList<String> getOnlineList() {
		ArrayList<String> list = new ArrayList<>();

		for (Entry<MsgWebSocket, String> entry : webSocketMap.entrySet()) {
			list.add(entry.getValue());
		}
		return list;
	}
}

  • OnlineCntSocket.java
package cn.tblack.ssm.socket;

import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import org.springframework.web.socket.server.standard.SpringConfigurator;

import cn.tblack.ssm.encoder.ClassEncoder;

/**
 * <span>用于发送实际在线人数的WebSocket</span>
 * 
 * @author TD唐登
 * @Date:2019年9月9日
 * @Version: 1.0(测试版)
 */
@ServerEndpoint(value = "/onlineCntSocket", configurator = SpringConfigurator.class, encoders = ClassEncoder.class)
public class OnlineCntSocket {

	/**
	 * @ 用于统计在线人数的Socket
	 */
	private static CopyOnWriteArraySet<OnlineCntSocket> webSocketSet = new CopyOnWriteArraySet<>();

	/* @拿到套接字的会话信息 */
	private Session session;

	@OnOpen
	/**
	 * @ 向连接成功的套接字发送在线人数
	 */
	public void onOpen(Session session) throws IOException {
		this.session = session;
		webSocketSet.add(this);

		sendMessageToAll();
		sendOnlineListToAll();
	}

	@OnClose
	/**
	 * @ 套接字断开连接。 向所有套接字发送信息
	 */
	public void onClose() throws IOException {
		webSocketSet.remove(this);
		sendMessageToAll();
		sendOnlineListToAll();
	}

	@OnMessage
	/**
	 * @ 接收到信息
	 */
	public void onMessage(String message, Session session) {
		
		if("_-_-1998610_ping_-_-".equals(message)) {
			try {
				session.getBasicRemote().sendText("ping");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	@OnError
	/**
	 * @ 发生错误
	 */
	public void onError(Session session, Throwable error) {
		System.out.println("发生错误: " + error);
	}

	/**
	 * @throws IOException @ 向所有当前活跃的套接字发送当前在线人数
	 */
	public void sendMessageToAll() throws IOException {

		for (OnlineCntSocket socket : webSocketSet) {
			socket.sendMessage(MsgWebSocket.getOnlineCount() + "");
		}
	}

	/**
	 * @throws IOException @ 向单个套接字发送数据
	 */
	public void sendMessage(String message) throws IOException {
		session.getBasicRemote().sendText(message);

	}

	/**
	 * @ 向全部套接字发送当前在线列表
	 */
	public void sendOnlineListToAll() {
		ArrayList<String> list = MsgWebSocket.getOnlineList();
		for (OnlineCntSocket onlineCntSocket : webSocketSet) {
			onlineCntSocket.sendOnlienList(list);
		}
	}

	/**
	 * @ 向单个套接字发送当前在线列表
	 */
	public void sendOnlienList(ArrayList<String> list) {
		try {
			session.getBasicRemote().sendObject(list);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (EncodeException e) {
			e.printStackTrace();
		}
	}

}

  • 类编码器
package cn.tblack.ssm.encoder;

import java.util.ArrayList;

import javax.websocket.EncodeException;
import javax.websocket.Encoder;
import javax.websocket.EndpointConfig;

import com.google.gson.Gson;

/**
 * <span>用于在 WebSocket 中传递对象所使用的编码器</span>
 * 
 * @author TD唐登
 * @Date:2019年9月10日
 * @Version: 1.0(测试版)
 */
public class ClassEncoder implements Encoder.Text<ArrayList<String>> {

	@Override
	public void init(EndpointConfig config) {
	}

	@Override
	public void destroy() {
	}

	@Override
	/**
	 * @ 编码器, 将Object对象类型转换为json类型进行返回
	 */
	public String encode(ArrayList<String> object) throws EncodeException {

		Gson gson = new Gson();

		return gson.toJson(object);
	}

}
  • 前端页面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"
	isELIgnored="false"%>

<!DOCTYPE HTML>
<html>
<head>
<title>聊天室</title>
<!-- Bootstrap -->
<link rel="stylesheet"
	href="http://cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<style type="text/css">
#text {
	margin-left: 15px;
}
</style>
</head>
<body>
	<div class="container-fluid">
		<div class="row">
			<div class="col-md-12">
				<div class="panel panel-primary">
					<div class="panel-heading">聊天室</div>
					<div id="msg" class="panel-body"></div>
					<div class="panel-footer">
						在线人数<span id="onlineCount"></span></div>

					<ul class="online-list" id="online-list">

					</ul>
				</div>
			</div>
		</div>
	</div>
	<input id="text" type="text" />
	<button onclick="send()">发送</button>
	<button onclick="closeWebSocket()">关闭连接</button>
	<div id="message"></div>
</body>

<script src="js/jquery.min.js"></script>

<script type="text/javascript">
	var msgWebSocket = null; //用于聊天使用的WebSocket
	var onlineCntSocket = null; //用于统计在线人数的WebSocket

	//判断当前浏览器是否支持WebSocket
	if ('WebSocket' in window) {
		msgWebSocket = new WebSocket(
				"ws://localhost:8080/chatRoom-ssm/msgWebSocket");
		onlineCntSocket = new WebSocket(
				"ws://localhost:8080/chatRoom-ssm/onlineCntSocket");

	} else {
		alert("对不起!你的浏览器不支持webSocket")
	}

	onlineCntSocket.onmessage = function(event) {

		//解析数据。 如果是 json 数据, 则表示接收到的为在线列表
		if (isJson(event.data)) {

			console.log("当前在线列表为: " + event.data);

			//将 Json 字符串转换为对象
			var array = JSON.parse(event.data);

			//清空之前的列表
			$("#online-list").empty();

			$("#online-list").append("<li class='list-span'>在线人数列表:</li>");
			//遍历对象并打印出在线人数列表
			for (var i = 0; i < array.length; i++) {

				$("#online-list").append("<li>" + array[i] + "</li>")
			}

		}
		//否则则表示接收到的就是在线人数
		else {
			
			//拿到后台返回的数据
			if(event.data == "ping"){
				heartCheck.reset().start();
				console.log("ping");
				return;
			}
			
			$("#onlineCount").text(event.data);
			console.log("在线人数: " + event.data);
		}

	}
	
	//连接发生错误的回调方法
	msgWebSocket.onerror = function() {
		setMessageInnerHTML("error");
	};
	//连接成功建立的回调方法
	msgWebSocket.onopen = function(event) {
		heartCheck.reset().start();
		console.log("event:" + event);
		setMessageInnerHTML("加入连接");
	};
	//接收到消息的回调方法
	msgWebSocket.onmessage = function(event) {
		setMessageInnerHTML(event.data);
	};
	//连接关闭的回调方法
	msgWebSocket.onclose = function() {
		setMessageInnerHTML("断开连接");
	};
	
	//监听窗口关闭事件,当窗口关闭时,主动去关闭msgWebSocket连接,
	// 防止连接还没断开就关闭窗口,server端会抛异常。
	window.onbeforeunload = function() {	
		msgWebSocket.close();
		onlineCntSocket.close();
	};
	//将消息显示在网页上
	function setMessageInnerHTML(innerHTML) {
		$("#msg").append(innerHTML + "<br/>")
	};
	
	//关闭连接
	function closeWebSocket() {
		heartCheck.reset();
		msgWebSocket.close();
		onlineCntSocket.close();
	}
	//发送消息
	function send() {
		var message = $("#text").val();
		msgWebSocket.send(message);
		$("#text").val("");
	}
	function sendMsg() {
		msgWebSocket.send(msg);
	}

	//判断是否是 json 数据
	function isJson(str) {
		try {
			if (typeof JSON.parse(str) == "object") {
				return true;
			}
		} catch (e) {

		}
		return false;
	}

	//心跳机制
	var heartCheck = {
		timeout : 5000, //5秒钟发一次心跳
		timeoutObj : null,
		serverTimeoutObj : null,
		reset : function() {
			clearTimeout(this.timeoutObj);
			clearTimeout(this.serverTimeoutObj);
			return this;
		},
		start : function() {
			var self = this;
			this.timeoutObj = setTimeout(function() {
				//这里发送一个心跳,后端收到后,返回一个心跳消息,
				//onmessage拿到返回的心跳就说明连接正常
				msgWebSocket.send("_-_-1998610_ping_-_-");
				onlineCntSocket.send("_-_-1998610_ping_-_-");
				self.serverTimeoutObj = setTimeout(function() {//如果超过一定时间还没重置,说明后端主动断开了
					closeWebSocket();
				}, self.timeout)
			}, this.timeout)
		}
	};
</script>
</html>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

David_TD

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值