10分钟快速上手WebScoket IM即时通信 SpringBoot +thymeleaf + WebScoket 前后端推送聊天示例奉上Demo

文章目录

  • 前言
    • 一、什么是WebScoket?
    • 二、为什么要使用WebScoket?
    • 三、应用场景
  • 正文
    • 后端
    • IM服务器
    • IndexController
    • 前端代码

前言

本文将以简洁的讲述带你快速上手webscoket

一、什么是WebScoket?

Websocket是一个持久化的协议而HTTP是非持久性的,它可以做到保持长时间连接,而HTTP在请求一次完就结束了
它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
WebSocket 的主要区别 此图来源于网络:
10分钟快速上手WebScoket IM即时通信 SpringBoot +thymeleaf + WebScoket 前后端推送聊天示例奉上Demo_第1张图片

二、为什么要使用WebScoket?

我们平常前端与后端交互时使用的都是HTTP协议,但HTTP是不支持持久连接的(长连接,循环连接的不算),例如我们需要做一个文件上传进度的回显,那么平常我们使用HTTP协议的话大部分都是通过轮询形式,每隔多长时间去访问一次后台 仔细想想这样做是对资源的一种浪费 一直在不断地在请求

webscoket的出现完美解决了这个问题,它与后端建立了一座桥梁 以持久性的形式保持连接,这样就能实时获取后台的数据。

三、应用场景

长连接形式适用一些实时性高的场景,例如:
1.社交 / 订阅
2.股票报价
3.音视频聊天
4.运动轨迹相关业务(定位应用)

正文

下面使用IM聊天的应用进行简单示例

后端

后端我们使用springboot后台 ,我们需要先导入webscoket相关的pom文件

		<dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-websocketartifactId>
        dependency>

开启webscoket支持

@Configuration
public class WebScoketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

注解说明:

//注解表示将该类升级为一个WebSocket服务端点
@ServerEndPoint
//收到客户端发来的消息时触发
@OnMessage
//客户端连接上服务端时触发
@OnOpen
//当连接关闭时触发
@OnClose
//发生错误时触发
@OnError

IM服务器

由于hashmap是线程不安全的使用ConcurrentHashMap来存储每个用户与服务器之间的会话。

1.有同学问不用考虑单例情况嘛?

单例在以下示例中并不会发生,这里的单例只是针对外部的webScoketSever,而我们的每个会话都已保存在了定义的map中并不会有影响

import java.io.IOException;
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.PathParam;
import javax.websocket.server.ServerEndpoint;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author CodingRem
 */
@ServerEndpoint("/imserver/{userId}")
@Component
public class WebScoketServer {

    static Logger logger = LoggerFactory.getLogger(WebScoketServer.class);
    /**
     * 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
     */

    private static int onlineCount = 0;
    /**
     * concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
     */

    private static ConcurrentHashMap<String, WebScoketServer> webSocketMap = new ConcurrentHashMap<>();
    /**
     * 与某个客户端的连接会话,需要通过它来给客户端发送数据
     */

    private Session session;

    /**
     * 接收userId
     */
    private String userId = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        //如果存在会话就删除重新创建
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
            webSocketMap.put(userId, this);
            //加入set中
        } else {
            webSocketMap.put(userId, this);
            //加入set中
            addOnlineCount();
            //在线数加1
        }

        logger.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());

        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            logger.error("用户:" + userId + ",网络异常!!!!!!");
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
            //从set中删除
            subOnlineCount();
        }
        logger.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param content 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String content, Session session) {
        logger.info("接收到来自id:" + userId + "的消息:" + content);
        //消息保存到数据库、redis
        if (StringUtils.isNotBlank(content)) {
            try {
                //解析JSON格式
                JSONObject jsonObject = JSON.parseObject(content);
                //为了保障安全 增加一个消息来源
                jsonObject.put("fromUserId", this.userId);
                String toId = jsonObject.getString("toId");
                //发送给对应用户
                if (StringUtils.isNotBlank(toId) && webSocketMap.containsKey(toId)) {
                    webSocketMap.get(toId).sendMessage(jsonObject.toJSONString());
                } else {
                    logger.error("ID为:" + toId + "的用户不在线");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
    }

    /**
     * 服务器主动推送
     */
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }


    /**
     * 主動發送消息
     */
    public static void sendInfo(String message, @PathParam("userId") String userId) throws IOException {
        logger.info("发送消息到:" + userId + ",报文:" + message);
        if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
            webSocketMap.get(userId).sendMessage(message);
        } else {
            logger.error("用户" + userId + ",不在线!");
        }
    }

    /***
     * 获取在线人数
     * @return
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebScoketServer.onlineCount++;
    }


    public static synchronized void subOnlineCount() {
        WebScoketServer.onlineCount--;
    }
}

IndexController

为了简化流程,这里我定义两条虚拟数据分别代表两个用户 张三李四通过名字首拼进行登录,xmd可以根据自己的需求进行改造

import com.rem.websocket.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;

import java.util.ArrayList;
import java.util.List;

@Controller
public class IndexController {

    @RequestMapping("/zs")
    String index(ModelMap map) {
        List<User> users = new ArrayList<>();
        users.add(new User(2,"李四"));
        users.add(new User(3,"王五"));
        map.put("users",users);
        map.put("user",new User(1,"张三"));
        return "index";
    }

    @RequestMapping("/ls")
    String index2(ModelMap map) {
        List<User> users = new ArrayList<>();
        users.add(new User(1,"张三"));
        users.add(new User(3,"王五"));
        map.put("users",users);
        map.put("user",new User(2,"李四"));
        return "index";
    }
}

前端代码

创建webscoket对象

var socketUrl = "http://localhost:80/imserver/" + $("#userId").val();
            socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
            console.log(socketUrl);
            if (socket != null) {
                socket.close();
                socket = null;
            }
            socket = new WebSocket(socketUrl);

协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

主要的调用事件:
scoket.onopen //连接时触发
scoket.onmessage //接收到消息时触发
scoket.onclose //关闭时触发
scoket.onerror //发生错误时触发

<script>

    $(function () {
        //初始化: active为选择联系人css  active-chat为对话显示css
        var person = $("ul li:first.person");
        var name = person.find("span.name").text();
        var fpId = person.attr("data-chat");
        $("div.top span.name").text(name);//初始的顶部显示
        var chat = $("div.chat[data-chat='" + fpId + "']");
        person.addClass("active");
        chat.addClass("active-chat");
        clickPerson(fpId);
       	//打开scoket连接
        openSocket();
    })

    var socket;

    function openSocket() {
        if (typeof (WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
        } else {
            console.log("您的浏览器支持WebSocket");
            var socketUrl = "http://localhost:80/imserver/" + $("#userId").val();
            socketUrl = socketUrl.replace("https", "ws").replace("http", "ws");
            console.log(socketUrl);
            if (socket != null) {
                socket.close();
                socket = null;
            }
            socket = new WebSocket(socketUrl);
            //连接时触发
            socket.onopen = function () {
                console.log("websocket已打开");
            };
            //接收到消息时触发
            socket.onmessage = function (msg) {
                var obj = msg.data;
                console.log(obj);
                if (isJson(obj)) {//验证是否是json格式
                    obj = JSON.parse(obj);
                    console.log("用户:" + obj.fromUserId + "发来消息");
                    //处理接收到的消息
                    receiveMsg(obj);
                } else {
                    console.log(obj);
                }

            };
            //关闭时触发
            socket.onclose = function () {
                console.log("websocket已关闭");
            };
            //发生错误时触发
            socket.onerror = function () {
                console.log("websocket发生了错误");
            }
        }
    }

    function isJson(str) {
        try {
            JSON.parse(str)
            return true
        } catch (e) {
            return false
        }
    }

    function receiveMsg(obj) {
        var chat = $(".chat[data-chat='" + obj.fromUserId + "']");
        chat.append("
" + obj.content + "
"
); } $(".write-link.send").click(function () { sendMessage(); var toId = $("#toId").val(); var chat = $("div.chat[data-chat='" + toId + "']"); var content = $("#content"); chat.append("
" + content.val() + "
"
); content.val(""); }); //发送消息 function sendMessage() { if (typeof (WebSocket) == "undefined") { console.log("您的浏览器不支持WebSocket"); } else { console.log("您的浏览器支持WebSocket"); console.log('{"toId":"' + $("#toId").val() + '","content":"' + $("#content").val() + '"}'); socket.send('{"toId":"' + $("#toId").val() + '","content":"' + $("#content").val() + '"}'); } } //更改目标id为当前选择的联系人对应id function clickPerson(id) { $("#toId").val(id); }
script>

至此这个示例已经完成,是不是感觉特别简单?让我们看看效果图
10分钟快速上手WebScoket IM即时通信 SpringBoot +thymeleaf + WebScoket 前后端推送聊天示例奉上Demo_第2张图片
本篇文章到此结束,如果这篇文章对你有帮助欢迎点赞、评论、交流、学习。
下载Demo

你可能感兴趣的:(java,jquery,spring,html)