SSM整合WebSocket实现网页版聊天室

WebSocket以及在SSM中的简单应用

WebSocket是为了解决由于http协议只能由客户端发起,服务端无法直接进行推送,导致服务端有持续的变化客户端想要获知就比较麻烦的问题。

WebSocket协议是基于TCP的一种网络协议,它实现了浏览器与服务器全双工通信,客户端和服务端都可以主动的推送消息,可以是文本也可以是二进制数据,而且没有同源策略的限制,不存在跨域问题。

这个过程可以描述为,在webscoket协议中, client利用http来建立TCP连接, 建立TCP连接之后, clientserver就可以基于TCP连接来通信。

WebSocket的应用有很多,比如消息推送,实况更新,即时通信等。这里实现一个简单的网页版即时通信功能。

以下配置和一些简单的接口是在之前的工程上添加,这里仅展示SSM结合WebSocket实现即时通信的功能的增加的部分。

添加依赖

<!-- 一下是Websocket需要配置的所有依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>4.1.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>4.1.0.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.3.1</versi
</dependency>

web.xml中对SpringMVC的配置

<!-- 配置DispatcherServlet -->
<servlet>
  <servlet-name>SpringMVC</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <!-- 配置springMVC需要加载的配置文件-->
  <init-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
  <async-supported>true</async-supported>
</servlet>
<servlet-mapping>
  <servlet-name>SpringMVC</servlet-name>
  <!-- 匹配所有请求,此处也可以配置成 / 形式 -->
  <url-pattern>*.do</url-pattern>
</servlet-mapping>

创建spring-websocket.xml用于springWebSocket的配置,这里是直接扫描注解来实现(WebSocket的三个Java文件均需置于这里配置的包下),当然也可以配置bean来实现,注释部分即为配置bean的样例

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">

    <!-- websocket处理类-->
    <!--<bean id="myHandler" class="com.cloneZjrt.webSocket.MyWebSocketHander"/>-->
    <!--握手接口/拦截器-->
    <!--<bean id="myInterceptor" class="com.cloneZjrt.webSocket.MyHandShakeInterceptor"/>-->

    <!--<websocket:handlers>-->
        <!--<websocket:mapping path="/websocket" handler="myHandler"/>-->
        <!--<websocket:handshake-interceptors >-->
            <!--<ref bean="myInterceptor"/>-->
        <!--</websocket:handshake-interceptors>-->
    <!--</websocket:handlers>-->

    <!-- 扫描webSocket包下所有使用注解的类型 -->
    <context:component-scan base-package="com.cloneZjrt.webSocket"/>

    <!-- 配置jsp 显示ViewResolver -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
        <property name="prefix" value="/"/>
        <property name="suffix" value=".jsp"/>
    </bean>
</beans>


在ApplicationContext.xml中注入spring-websocket.xml,使web.xml可以扫描到spring-websocket.xml

<import resource="classpath*:/spring/spring-websocket.xml"/>

WebSocket拦截器

/**
 * websocket拦截器
 * 拦截握手前,握手后的两个切面
 * 目前暂做简单的拦截
 */
@Component
public class MyHandShakeInterceptor implements HandshakeInterceptor {

    /**
     * 握手之前,若返回false,则不建立链接
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse,
                                   WebSocketHandler webSocketHandler, Map<String, Object> map) throws Exception {
        System.out.println("=====!!!!");
        if(serverHttpRequest instanceof ServletServerHttpRequest){
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) serverHttpRequest;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            if(null != session.getAttribute("user")){
                UserEntity user = (UserEntity) session.getAttribute("user");
                map.put("ws_user", user);
                System.out.println(user);
            }else{
                System.out.println("握手=======失败");
                return false;
            }
        }
        System.out.println("--------------握手开始...");
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
        System.out.println("--------------握手成功啦...");
    }
}

spring中对WebSocket的注解配置(这里的注释对应xml文件中的bean配置)

@Component
@EnableWebSocket
//@Configuration
public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

    @Autowired
    private MyWebSocketHander myWebSocketHander;

//	  websocket 处理类
//    @Bean
//    public MyWebSocketHander myWebSocketHandler(){
//        return new MyWebSocketHander();
//    }

    private static final String LINK_URI = "websocket.do";
    //添加websocket处理器,添加握手拦截器  拦截器先执行 然后到处理器
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(myWebSocketHander,LINK_URI).addInterceptors(new MyHandShakeInterceptor());
    }
}

创建MyWebSocketHander类继承WebSocketHandler类,用来处理消息的接收和发送

@Component
public class MyWebSocketHander implements WebSocketHandler {

    //在线用户的SOCKETsession(存储了所有的通信通道)
    public static final Map<UserEntity, WebSocketSession> USER_SOCKETSESSION_MAP;

    //存储所有的在线用户
    static {
        USER_SOCKETSESSION_MAP = new HashMap<UserEntity, WebSocketSession>();
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception {

        //这里的"ws_user"对应HandshakeInterceptor中配置的
        UserEntity user = (UserEntity) webSocketSession.getAttributes().get("ws_user");
        USER_SOCKETSESSION_MAP.put(user,webSocketSession);

        //这里传到前端的应该是JSON格式
        String messageFormat = "{onlineNum:\"%d\",userName:\"%s\" , msgTyp " + ":\"%s\"}";
        String msg = String.format(messageFormat, USER_SOCKETSESSION_MAP.size(), USER_SOCKETSESSION_MAP.keySet().toString(), "notice");

        TextMessage testMsg = new TextMessage(msg + "");

        sendMessageToAll(testMsg);
    }

    /**
     * 群发信息:给所有在线用户发送消息
     * @param message
     */
    private void sendMessageToAll(final TextMessage message){
        //对用户发送的消息内容进行转义

        //获取到所有在线用户的SocketSession对象
        Set<Map.Entry<UserEntity, WebSocketSession>> entrySet = USER_SOCKETSESSION_MAP.entrySet();
        for (Map.Entry<UserEntity, WebSocketSession> entry : entrySet) {
            //某用户的WebSocketSession
            final WebSocketSession webSocketSession = entry.getValue();
            //判断连接是否仍然打开的
            if(webSocketSession.isOpen()){
                //开启多线程发送消息(效率高)
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            if (webSocketSession.isOpen()) {
                                webSocketSession.sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    }

    /**
     *
     * 说明:给某个人发信息
     * @param UserEntity
     * @param message
     */
    private void sendMessageToUser(UserEntity UserEntity, TextMessage message) throws IOException{
        //获取到要接收消息的用户的session
        WebSocketSession webSocketSession = USER_SOCKETSESSION_MAP.get(UserEntity);
        if (webSocketSession != null && webSocketSession.isOpen()) {
            //发送消息
            webSocketSession.sendMessage(message);
        }
    }

    /**
     * 客户端发送服务器的消息时的处理函数,在这里收到消息之后可以分发消息
     */
    @Autowired
    private MessageService messageService;

    @Autowired
    private UserService userService;

    @Override
    public void handleMessage(WebSocketSession webSocketSession,
                              WebSocketMessage<?> webSocketMessage) throws Exception {

        String messageFormat = null;
        FileOutputStream output;

        System.out.println(webSocketSession.getAttributes().get("ws_user"));
        //发送消息的时间
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String sendMsgDate = dateFormat.format(new Date());

        UserEntity user = (UserEntity) webSocketSession.getAttributes().get("ws_user");

        String msgContent = webSocketMessage.getPayload() + "";
        System.out.println("前端传到后台的数据 " + msgContent);
        JSONObject chat = JSON.parseObject(msgContent);
        //消息的内容
        String msgJSON = chat.get("message").toString();
        //消息接收者名字
        String receiveUserName = chat.get("receive").toString();
        //消息的样式
        String msgJSONType = chat.get("type").toString();
        String exit = "exit";
        String MessageEntity = "MessageEntity";
        String img = "img";

        System.out.println("JSON验证" + chat);
        System.out.println(chat.get("type").toString());

        if (msgJSONType.equals(exit)) {
            messageFormat = "{onlineNum:\"%d\",userName:\"%s\" ,userNameList:\"%s\", msgTyp:\"%s\"}";
            //从用户列表中移除已退出的用户
            USER_SOCKETSESSION_MAP.remove(user);

            String msg = String.format(messageFormat, USER_SOCKETSESSION_MAP.size()-1 ,msgJSON ,USER_SOCKETSESSION_MAP.keySet().toString(),"exit");
            TextMessage testMsg = new TextMessage(msg + "");
            sendMessageToAll(testMsg);

        } else if (msgJSONType.equals(MessageEntity)) {

            MessageEntity messageEntity = new MessageEntity(user.getUserId(), sendMsgDate, msgContent, user.getUserId(), "msg");
            messageService.addMessage(messageEntity);

            messageFormat = "{UserEntity:\"%s\",sendDate:\"%s\" ," + "sendContent:\"%s\" , msgTyp :\"%s\"}";

            if(!receiveUserName.equals("") || receiveUserName!=null){

                String message = String.format(messageFormat, user.getUserName(), sendMsgDate, msgJSON , "msg");
                TextMessage testMsg = new TextMessage(message + "");
                sendMessageToUser(userService.getUserByName(receiveUserName),testMsg);
                if(!receiveUserName.equals(user.getUserName())){
                    sendMessageToUser(user,testMsg);
                }

            }else {

                String message = String.format(messageFormat, user.getUserName(), sendMsgDate, msgJSON , "msg");
                TextMessage testMsg = new TextMessage(message + "");
                //确保每个用户信息都能同步到
                sendMessageToAll(testMsg);
            }
        }else if(msgJSONType.equals(img)){
            System.out.println("send pic");
            //设置图片保存路径
            output = new FileOutputStream(new File("D:\\images\\"+chat.get("filename").toString().split(":")[0]));
            System.out.println("图片路径"+"D:\\images\\"+chat.get("filename").toString().split(":")[0]);
            output.close();
        }
    }

    @Override
    public void handleTransportError(WebSocketSession webSocketSession,
                                     Throwable throwable) throws Exception {

        // 记录日志,准备关闭连接
        System.out.println("Websocket异常断开:" + webSocketSession.getId() + "已经关闭");

        //一旦发生异常,强制用户下线,关闭session
        if (webSocketSession.isOpen()) {
            webSocketSession.close();
        }

        UserEntity user = (UserEntity) webSocketSession.getAttributes().get("ws_user");
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String sendMsgDate = dateFormat.format(new Date());


        String messageFormat = "{UserEntity:\"%s\",sendDate:\"%s\" ," + "sendContent:\"%s\" , msgTyp :\"%s\"}";
        String message = String.format(messageFormat, user.getUserName(), sendMsgDate, "万众瞩目的【"+user.getUserName()+"】退出了群聊!" , "msg");

        System.out.println(message);

        TextMessage testMsg = new TextMessage(message + "");
        //确保每个用户信息都能同步到
        sendMessageToAll(testMsg);
        Collection<WebSocketSession> values = USER_SOCKETSESSION_MAP.values();
        values.remove(webSocketSession);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession,
                                      CloseStatus closeStatus) throws Exception {
        // 记录日志,准备关闭连接
        System.out.println("Websocket正常断开:" + webSocketSession.getId() + "已经关闭");

        UserEntity userRemove = (UserEntity) webSocketSession.getAttributes().get("ws_user");
        USER_SOCKETSESSION_MAP.remove(userRemove);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

之后是操作层

@Controller
@RequestMapping("/user")
@ResponseBody
public class UserController {

    @Resource
    private UserService userService;

    @RequestMapping("getUser")
    public @ResponseBody UserEntity getUser(Long userId) {

        return userService.getUserById(userId);

    }

    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String login() {
        return "login";
    }

    @RequestMapping(value = "/login", method = RequestMethod.POST)
    public ModelAndView loginUser(String userName, HttpSession session) {
        System.out.println(userName);
        if(userService.getUserByName(userName) == null){
            try{
                userService.register(new UserEntity(userName));
            }catch (Exception e){
                System.out.println(e.getMessage());
            }
        }
        UserEntity user = userService.getUserByName(userName);
        session.setAttribute("user",user);
        return new ModelAndView("chat");
    }

}

JSP页面

login.jsp用于用户登陆

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%String path = request.getContextPath();%>
<html>
<link rel="stylesheet" href="static/css/bootstrap.min.css" type="text/css">
<script src="static/js/jquery.min.js"></script>
<script src="static/js/bootstrap.min.js" type="text/javascript"></script>
<body>

<div class="container">
    <form action="user/login.do" method="post" name="loginForm" onsubmit="return validateForm()">
        <div class="form-group col-sm-8">
            <label for="user">用户名</label>
            <input type="text" class="form-control" name="userName" maxlength="10" id="user"><br/>
            <p id="uNClass"></p>
        </div>

        <div id="buttonBox">
            <input type="submit" value="登录" class="btn btn-info btn-lg col-sm-2" style="margin-right: 3%;">
        </div>
    </form>
</div>

</body>

<script type="text/javascript">
    function validateForm() {
        var name = document.forms["loginForm"]["userName"].value;
        if (name == null || name == "") {
            text = "输入不能为空";
            document.getElementById("uNClass").innerHTML = text;
            return false;
        }
    }
</script>

</html>

chat.jsp聊天界面

<%--
  Created by IntelliJ IDEA.
  User: 98031
  Date: 2018/10/1
  Time: 12:24
  To change this template use File | Settings | File Templates.
--%>
<!--
* 客户端连接服务端websocket
* 并且订阅一系列频道,用来接收不同种类的消息
* /app/chat/participants :当前在线人数的消息,只会接收一次
* /topic/login : 新登录用户的消息
* /topic/chat/message : 聊天内容消息
* /topic/logout : 用户离线的消息
* 服务器发回json实例 {"userName":"chris","sendDate":1494664021793,"content":"hello"}
-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<% String path = request.getContextPath();
    String socketPath = request.getServerName() + ":" + request.getServerPort() + path + "/";
%>
<html>
<head>
    <title>Start chatting now</title>
</head>
<link rel="stylesheet" href="<%=path%>/static/css/bootstrap.min.css"
      type="text/css">
<link rel="stylesheet" href="<%=path%>/static/css/chat.css" type="text/css">
<script src="<%=path%>/static/js/jquery.min.js"></script>
<script src="<%=path%>/static/js/bootstrap.min.js"
        type="text/javascript"></script>
<body>

<div id="container" style="width:500px">

    <div id="header">
        <h1 id="title">chat-room</h1></div>
    <div class="middle">
        <div id="menu">
            <!--   <b id="pOnline">在线人数<span>0</span></b> -->
            <p id="tou">欢迎来到聊天室</p>
            <br>在线用户人数<br>
            <p id="onlineNum">0</p>
            <p>当前在线用户</p>
            <div id="onlineUser"></div>

        </div>

        <div class="chatter" id="chatter">
            <p id="msg"></p>

        </div>
    </div>
    <div id="content">
        <textarea class="form-control" rows="3" placeholder="我想说....."
                  id="msgContent">
        </textarea>
    </div>

    <div style="background-color: #F8F8F8;">
        <div id="buttons">
            <button id="butSent" type="button" class="btn btn-default"
                    onclick="getConnection()">连接
            </button>
            <button type="button" class="btn btn-default"
                    onclick="sendMsgClose()">断开
            </button>
            <button type="button" class="btn btn-default" onclick="sendMsg()">
                发送
            </button>
            <button type="button" class="btn btn-default" onclick="clearMsg()">
                清屏
            </button>
            <button type="button" class="btn btn-default" data-toggle="modal"
                    data-target="#imgModal">
                上传图片
            </button>
            <input type="text" name="receiveUserName" id="receiveUserName" value=""><br/>
            <input type="file" name="userImage" id="userImage1"
                   onchange="check()" placeholder="请选择要上传的图片">
            <button type="button" class="btn btn-default"
                    onclick="sendImg()">
                发送图片
            </button>
            <button type="button" class="btn btn-default"
                    onclick="location.href='http://localhost:8080/chatroom/chatHistory.jsp'">
                消息记录
            </button>
        </div>
    </div>

    <div id="footer">
        Designed by Annie
    </div>
</div>
</div>
</body>
<script>

    var wsServer = null;
    wsServer = "ws://" + location.host + "${pageContext.request.contextPath}" + "/websocket.do";
    var websocket = null;

    //从后台接受聊天消息,并展示到前端
    function showChat(evnt) {
        var message = eval("(" + evnt.data + ")");
        var msg = $("#msg");
        //msg.html是之前的聊天内容,空一行
        msg.html(msg.html() + "<br/>" + "用户: " + message.user + " 发送时间:" + message.sendDate + "<br/>" + message.sendContent);
    }

    //打开链接
    function getConnection() {
        if (websocket == null) {
            websocket = new WebSocket(wsServer);
            websocket.onopen = function (evnt) {
                alert("链接服务器成功!");
            };
            websocket.onmessage = function (evnt) {
                var onlineUser = $("#onlineUser");
                var message = eval("(" + evnt.data + ")");
                //显示在线人数及在线用户
                if (message.msgTyp === "notice") {
                    var htmlOnline;
                    $("#onlineNum").text(message.onlineNum);
                    htmlOnline = "<p> " + message.userName + " </p>";
                    //实时更新在线用户
                    onlineUser.html("");
                    $(onlineUser).append(htmlOnline);
                } else if (message.msgTyp === "msg") {
                    showChat(evnt);
                } else if (message.msgTyp === "exit") {
                    $("#onlineNum").text(message.onlineNum);
                    var msg = $("#msg");
                    //msg.html是之前的聊天内容,空一行
                    msg.html(msg.html() + "<br/>" + "用户: " + message.userName + "退出聊天");
                }
            };
            websocket.onerror = function (evnt) {
                alert("发生错误,与服务器断开了链接!")
            };
            websocket.onclose = function (evnt) {
                alert("与服务器断开了链接!")
            };
            $('#send').bind('click', function () {
                send();
            });
        } else {
            alert("连接已存在!")
        }
    }

    function closeConnection() {
        if (websocket != null) {
            websocket.close();
            websocket = null;
            alert("已经关闭连接")
        } else {
            alert("未开启连接")
        }
    }

    function sendMsgClose() {
        var closeMsg = "${user.userName}";
        websocket.send(JSON.stringify({
            message: closeMsg,
            type: "exit"
        }));
        websocket.onmessage = function (evnt) {
            var onlineUser = $("#onlineUser");
            var message = eval("(" + evnt.data + ")");
            //显示在线人数及在线用户
            if (message.msgTyp === "exit") {
                $("#onlineNum").text(message.onlineNum);
                var msg = $("#msg");
                //msg.html是之前的聊天内容,空一行
                msg.html(msg.html() + "<br/>" + "用户: " + message.userName + "退出聊天");
                htmlOnline = "<p> " + message.userNameList + " </p>";
                onlineUser.html("");
                $(onlineUser).append(htmlOnline);
                closeConnection();
            }
        }
    }

    function sendImg() {
        var inputElement = document.getElementById("userImage1");
        var fileList = inputElement.files;
        var file = fileList[0];
        if (!file) return;
        var reader = new FileReader();
        //以二进制形式读取文件
        reader.readAsArrayBuffer(file);
        //文件读取完毕后该函数响应
        reader.onload = function loaded() {
            var blob = document.getElementById("userImage1").files[0];
            //发送二进制表示的文件
            websocket.send(JSON.stringify({
                message: blob,
                filename: file.name,
                type: "img"
            }));
            inputElement.outerHTML = inputElement.outerHTML; //清空<input type="file">的值
        }
    }


    function sendMsg() {
        var msg = $("#msgContent");
        var receiveUserName = document.getElementById("receiveUserName").value;
        if (websocket == null) {
            alert("连接未开启!");
            return;
        }
        var message = msg.val();
        //输入完成后,清空输入区
        msg.val("");
        if (message == null || message === "") {
            alert("输入不能为空的哦");
            return;
        }
        //向后台MyWebSocketHandler中的handlemessage发送信息
        websocket.send(JSON.stringify({
            message: message,
            type: "chatMsg",
            receive: receiveUserName,
        }));
    }

    //清屏函数
    function clearMsg() {
        $("#chatter").html("");
    }

</script>

</html>

这样,大致的聊天室就完成了

代码参考自博文:SSM+WebSocket实现一个简易网页版通讯工具

博客中提供源码,可下载尝试运行

  • 2
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值