WebSocket是一种在单个TCP连接上进行全双工通信的协议。顾名思义我们的使用经常就是用于进行实时的消息推送,今天我们是用SpringBoot来实现简易的聊天室,其中包括私聊和群聊。
首先是先把自己需要的界面弄好,我就简单的制作了一下
讲解一下界面的设计,首先我们是需要有发送方和接收方,我是用用户id来记录,然后他们都是需要用session来保存,相当于是系统的用户在线状态嘛,所以首先步骤一,要进行用户登录(创建连接),然后就是请求获取设计好的websocket服务,连接好了服务就是相当于在一个聊天室内,如果没有指定接收方时就是群聊,有接收方就是私聊。
接下来就是代码啦,先给出html的代码:
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<html>
<head>
<meta charset="utf-8" />
<title>WebSockettitle>
head>
<script src="jquery-3.4.1.min.js">script>
<script type="text/javascript" src="websocket.js">script>
<script type="text/javascript" src="mine.js">script>
<style>
#responseText{
width:237px;
height: 200px;
border: 1px solid;
}
.userId{
width: 110px;
}
style>
<body>
<input type="text" id="sendId" class="userId" placeholder="输入连接的用户ID" />
<button onclick="checkTextById('sendId');createUser();">创建连接button>
<button onclick="checkTextById('sendId');openSocket();">请求连接button><br /><br />
<input type="text" id="requestText" placeholder="输入你的内容" />
<input type="text" id="receiveId" class="userId" placeholder="输入接收的用户ID" />
<button onclick="checkTextById('requestText');sendMessage();">发送消息button><br /><br />
<div id="responseText">聊天室:div><br />
<span id="serverTxt">尚未连接服务span>
body>
html>
js代码:
//websocket.js
var socket;//注意:360浏览器不支持websocket
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
var userId = document.getElementById('sendId').value;
var socketUrl = "ws://localhost:19429/webSocket/" + userId;
console.log(socketUrl);
if (socket != null) {
socket.close();
socket = null;
}
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function () {
console.log("websocket已打开");
document.getElementById('serverTxt').innerText = userId+"已连接websocket";
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function (msg) {
var serverMsg = "收到服务端信息:" + msg.data;
console.log("类型:" + typeof (msg.data));
var msgJson = eval('(' + msg.data + ')');
var div = document.createElement("div");
var responseText = document.getElementById('responseText');
var type = "(私聊)";
if (null == msgJson.toUserId || msgJson.toUserId == "") {
type = "(群聊)";
}
div.innerHTML = msgJson.userId+type+":"+msgJson.contentText;
responseText.appendChild(div);
console.log(serverMsg);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
socket.onclose = function () {
console.log("websocket已关闭");
document.getElementById('serverTxt').innerText = "尚未连接服务";
};
//发生了错误事件
socket.onerror = function () {
console.log("websocket发生了错误");
}
}
}
//websocket发送消息
function sendMessage() {
var toUserId = document.getElementById('receiveId').value;//接收的用户ID
var userId = document.getElementById('sendId').value;//发送的用户ID
var contentText = document.getElementById('requestText').value;
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
// console.log("您的浏览器支持WebSocket");
if (userId.trim().length>0 && contentText.trim().length>0){
var msg = '{"userId":"'+userId+'","toUserId":"'+toUserId+'","contentText":"'+contentText+'"}';
console.log(msg);
socket.send(msg);
if (toUserId.trim().length>0){
var responseText = document.getElementById('responseText');
var div = document.createElement("div");
div.innerHTML = userId+"(私聊):"+contentText;
responseText.appendChild(div);
}
alert("发送成功!");
}else {
alert("发送失败!");
}
}
}
//mine.js
//通过id判断文本组件是否为空
function checkTextById(id) {
var Txt = document.getElementById(id);
//判断是否存在这个组件
if (Txt!=null){
if (Txt.value.trim().length==0){
alert("内容不允许为空!");
}
}else{
alert("id为"+id+"组件不存在!");
}
}
function createUser() {
var userId = document.getElementById("sendId").value;
if (userId.trim().length>0) {
$.ajax({
url: "/createUser",
async: true,
type: "POST",
data: {"userId":userId},
success: function (data) {
if (data=="1"){
document.getElementById('serverTxt').innerText = userId+"已创建";
}
},
error: function () {
alert("请求失败");
},
dataType: "text"
});
}else{
alert("创建用户id不能为空!");
}
}
java代码部分我是通过搭建SpringBoot项目的,不过不影响使用,先贴出代码部分,首先需要有转换session的工具类,然后还需要登陆(创建session),用户实体类只需要弄一个uuid即可。
配置类以及转换类:
//配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebConfig {
/**
* 支持websocket
* 如果不使用内置tomcat,则无需配置
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
//转换session
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
/**
* 用于从websocket中获取用户session
*/
public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
sec.getUserProperties().put(HttpSession.class.getName(), httpSession);
}
}
websocket服务:
import com.alibaba.fastjson.JSONObject;
import com.example.demo.config.HttpSessionConfigurator;
import com.example.demo.po.User;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
//注入 HttpSessionConfigurator.class配置httpSession类
@ServerEndpoint(value = "/webSocket/{sid}",configurator = HttpSessionConfigurator.class)
@Component
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger onlineNum = new AtomicInteger();
//concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。
private static ConcurrentHashMap<String, Session> sessionPools = new ConcurrentHashMap<>();
//发送消息
public void sendMessage(Session session, String message) throws IOException {
if(session != null){
synchronized (session) {
// System.out.println("发送数据:" + message);
session.getBasicRemote().sendText(message);
}
}
}
//给指定用户发送信息
public void sendInfo(String userId, String message){
Session session = sessionPools.get(userId);
try {
if (sessionPools.get(userId) != null){
sendMessage(session, message);
}else{
System.out.println("用户"+userId+"不在线");
}
}catch (Exception e){
e.printStackTrace();
}
}
//建立连接成功调用
@OnOpen
public void onOpen(Session session, @PathParam(value = "sid") String userId, EndpointConfig config){
try {
HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
User user = (User) httpSession.getAttribute("userSession");
//判断是否还需加入连接
if (user!=null && !sessionPools.containsKey(user.getUuid())){
sessionPools.put(String.valueOf(user.getUuid()), session);
addOnlineCount();
System.out.println(userId + "加入webSocket!当前人数为" + onlineNum);
sendMessage(session, "欢迎" + user.getUuid() + "加入连接!");
}else{
System.out.println("没有连接加入,当前人数为"+onlineNum);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//关闭连接时调用
@OnClose
public void onClose(@PathParam(value = "sid") String userId){
//判断是否有连接
if (sessionPools.containsKey(userId)){
sessionPools.remove(userId);
subOnlineCount();
System.out.println(userId + "断开webSocket连接!当前人数为" + onlineNum);
}else{
System.out.println("用户"+userId+"不存在连接,无需断开连接");
}
}
//收到客户端信息
@OnMessage
public void onMessage(String message){
try {
//转换成为JSONObject对象
JSONObject jsonObj = JSONObject.parseObject(message);
if (jsonObj.containsKey("toUserId") && !"".equals(jsonObj.get("toUserId"))){
//指定用户
sendInfo((String) jsonObj.get("toUserId"),message);
}else{
//群发
for (Session session: sessionPools.values()) {
try {
sendMessage(session, message);
} catch(Exception e){
e.printStackTrace();
continue;
}
}
}
} catch(Exception e){
e.printStackTrace();
}
}
//错误时调用
@OnError
public void onError(Session session, Throwable throwable){
System.out.println("发生错误");
throwable.printStackTrace();
}
//增加在线人数
public static void addOnlineCount(){
onlineNum.incrementAndGet();
}
//减少在线人数
public static void subOnlineCount() {
onlineNum.decrementAndGet();
}
}
最后效果如下:
需要注意的是:
1.需要先创建连接(即创建session)
2.创建连接后要连接服务(加入至聊天室)
3.不指定接收方则是群聊
代码在资源里O(∩_∩)O