zoukankan      html  css  js  c++  java
  • Websocket --(3)实现

    今天介绍另外一种websocket实现方式,结合了spring MVC,并完善了第二节所提到做一个简单的登录认证用来识别用户的名称。界面继续沿用第二节的布局样式,同时增加上线和下线功能。

    参考了 https://blog.csdn.net/mybook201314/article/details/70173674 这篇文章,但是同时做了部分改进。更多内容请看https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket  这是spring 官方文档,相当详细,我做的过程中更多的是看官方介绍。

    1.环境

    Eclisp + jdk1.7 +tomcat 8,低版本的Tomcat7可能跑不起来,项目采用maven构建。

    2.jar 包(只写websocket部分,spring 核心包就不写了)

            <dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-websocket</artifactId>
               <version>4.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-messaging</artifactId>
               <version>4.0.2.RELEASE</version>
            </dependency>
            
            
        <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.1.0</version>
           </dependency>
           
            <dependency>
                 <groupId>com.alibaba</groupId>
                 <artifactId>fastjson</artifactId>
                 <version>1.1.23</version>
           </dependency>

    这里添加了阿里巴巴的fastjson来处理json字符串。当然不用这个也可以。

    web.xml 配置如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
             xmlns="http://java.sun.com/xml/ns/javaee" 
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
             id="WebApp_ID" version="3.0">
        <absolute-ordering />
        <display-name>websocket2</display-name>
      <welcome-file-list>
        <welcome-file>login.jsp</welcome-file>
        <welcome-file>index.html</welcome-file>
        <welcome-file>index.htm</welcome-file>
        <welcome-file>index.jsp</welcome-file>
        <welcome-file>default.html</welcome-file>
        <welcome-file>default.htm</welcome-file>
        <welcome-file>default.jsp</welcome-file>
      </welcome-file-list>
      
        <!-- Spring配置 -->
      <context-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-content.xml</param-value>
        </context-param>
    
        <listener>
            <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
        </listener>
      
    
        <!-- Spring MVC配置 -->
        <servlet>
            <servlet-name>spring</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!-- 可以自定义servlet.xml配置文件的位置和名称,默认为WEB-INF目录下,名称为[<servlet-name>]-servlet.xml,如spring-servlet.xml -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring-mvc.xml</param-value>
            </init-param>
            <async-supported>true</async-supported>
            <load-on-startup>1</load-on-startup>
        </servlet>
    
        <servlet-mapping>
            <servlet-name>spring</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!-- 中文过滤器 -->
        <filter>
            <filter-name>CharacterEncodingFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
            <init-param>
                <param-name>forceEncoding</param-name>
                <param-value>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>CharacterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        
        <servlet-mapping>  
                <servlet-name>default</servlet-name>  
                <url-pattern>*.css</url-pattern>  
        </servlet-mapping>
        <servlet-mapping>  
                <servlet-name>default</servlet-name>  
                <url-pattern>*.gif</url-pattern>  
        </servlet-mapping>
        <servlet-mapping>  
                <servlet-name>default</servlet-name>  
                <url-pattern>*.jpg</url-pattern>  
        </servlet-mapping>  
        <servlet-mapping>  
                <servlet-name>default</servlet-name>  
                <url-pattern>*.js</url-pattern>  
        </servlet-mapping>  
        <servlet-mapping> 
                <servlet-name>default</servlet-name> 
                <url-pattern>*.html</url-pattern> 
        </servlet-mapping>
        
    </web-app>

    web.xml 里面有三点需要注意:命名空间要3.0及以上,添加配置文件开头添加<absolute-ordering />,具体可参看官方文档 Deployment 这个章节。

    构建完成的项目如下图所示

    没有用的文件请自行忽略

    3.实现

    首先新建一个自己的websocket处理类,这个类需要实现WebSocketHandler 这个接口,当然根据官方文档 里面的这句话Creating a WebSocket server is as simple as implementing WebSocketHandler or more likely extending either TextWebSocketHandler or BinaryWebSocketHandler 也可以实现TextWebSocketHandler或者BinaryWebSocketHandler。这个接口里面有几个方法分别对应连接前,连接成功后,和消息处理,关闭连接等操作。

    package com.lzl.ws;
    
    import java.util.ArrayList;
    import java.util.Map;
    
    import org.apache.log4j.Logger;
    import org.springframework.web.socket.CloseStatus;
    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketHandler;
    import org.springframework.web.socket.WebSocketMessage;
    import org.springframework.web.socket.WebSocketSession;
    
    import com.alibaba.fastjson.JSON;
    
    //websocket处理类
    public class MyWebSocketHandler implements WebSocketHandler{
        private static final Logger log = Logger.getLogger(MyWebSocketHandler.class);
    
        // 保存所有的用户session
        private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();
    
        
        //连接关闭后
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
                throws Exception {
            // TODO Auto-generated method stub
            log.info("已经关闭连接。。。。");
            users.remove(session);
        }
        
        //连接就绪后
        @Override
        public void afterConnectionEstablished(WebSocketSession session)
                throws Exception {
            log.info("已经成功连接。。。sessionID是"+session.getId());
            users.add(session);
            
        }
        
        //处理信息
        @Override
        public void handleMessage(WebSocketSession session, WebSocketMessage<?> message)
                throws Exception {
            log.info("收到消息。。。sessionID:"+session.getId());
            log.info("消息内容:"+message.getPayload().toString());
            Map<String,Object > map = JSON.parseObject(message.getPayload().toString(),Map.class);
            TextMessage textMessage = new TextMessage(map.get("userID").toString()+":"+map.get("msgContent").toString(),true);
            log.info("转发:"+textMessage.getPayload().toString());
            for(WebSocketSession user:users){
                user.sendMessage(textMessage);
            }
            log.info("发送完成。。。。");
        }
        
        //发生错误
        @Override
        public void handleTransportError(WebSocketSession session, Throwable throwable)
                throws Exception {
            // TODO Auto-generated method stub
            log.info("发生错误。。。。"+throwable.getMessage()+"*****SessionID"+session.getId());
        }
    
        @Override
        public boolean supportsPartialMessages() {
            // TODO Auto-generated method stub
            return false;
        }
    
    }

    我这里是把消息发送给所有已经连接的客户了

    接下来新建websocket注册类

    package com.lzl.ws;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    import org.springframework.web.socket.config.annotation.EnableWebSocket;
    import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
    import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
    
    
    @Configuration
    @EnableWebMvc
    @EnableWebSocket
    public class MyWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{
    
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            // TODO Auto-generated method stub
            registry.addHandler(new MyWebSocketHandler(), "/websocket").addInterceptors(new HttpSessionHandshakeInterceptor());
            
        }
    
    }

    这个类一定 要在spring MVC 自动扫描的包下面。其中 HttpSessionHandshakeInterceptor 类称为握手操作类,可以自己新建一个类去继承他,这里面可以根据需要写一些建立连接前,和建立连接后的动作,我这里暂时没有用到,所有就直接调用父类了。

    最后一步,在spring 配置文件中配置websocket 

    <?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:aop="http://www.springframework.org/schema/aop"
        xmlns:websocket="http://www.springframework.org/schema/websocket"
        xsi:schemaLocation="    
               http://www.springframework.org/schema/beans    
               http://www.springframework.org/schema/beans/spring-beans-3.0.xsd    
               http://www.springframework.org/schema/aop    
               http://www.springframework.org/schema/aop/spring-aop-3.0.xsd  
               http://www.springframework.org/schema/context    
               http://www.springframework.org/schema/context/spring-context-3.0.xsd
               http://www.springframework.org/schema/websocket
               http://www.springframework.org/schema/websocket/spring-websocket.xsd">
               
           
         <!-- 引入jdbc配置文件 -->    
       <!-- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">  
            <property name="locations">  
                <list>  
                   <value>classpath:application.properties</value>  
                </list>  
            </property>  
        </bean>  --> 
        <context:property-placeholder location="classpath:application.properties"/> 
        <!-- 自动扫描注解的bean -->  
        <context:component-scan base-package="com.lzl" /> 
         
        <!-- 配置数据源 -->  
        <bean id="dataSource"  class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.user}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>  
      
        <!-- 配置Mybatis的文件 ,mapperLocations配置**Mapper.xml文件位置,configLocation配置mybatis-config文件位置-->  
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
            <property name="dataSource" ref="dataSource" />  
            <property name="mapperLocations" value="classpath:mapper/*.xml"/>    
            <property name="configLocation" value="classpath:mybatis-config.xml" />  
        </bean>  
      
        <!-- 自动扫描了所有的XxxxMapper.xml对应的mapper接口文件,这样就不用一个一个手动配置Mpper的映射了,只要Mapper接口类和Mapper映射文件对应起来就可以了。 -->  
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
            <property name="basePackage"   value="com.lzl.dao" /> 
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property> 
        </bean>  
        
        
        <bean id = "Handler" class = "com.lzl.ws.MyWebSocketHandler"></bean>
        <websocket:handlers>
            <websocket:mapping path="/websocket" handler="Handler"/>
            <websocket:handshake-interceptors>
                <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
            </websocket:handshake-interceptors>
        </websocket:handlers>
        
          
    </beans>  

    这里需要注意的是在在头文件添加webcoket支持。对于不支持的websocket的浏览器可以参照官方文档的  SockJS Fallback 这个章节处理,或者参考https://blog.csdn.net/mybook201314/article/details/70173674

    至此,websocket 相关内容完成,接着做登录操作。登录这里很简单,直接将用户名称和聊天页面的地址返回。

    package com.lzl.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.servlet.ModelAndView;
    
    import com.lzl.dao.User;
    
    @Controller
    @RequestMapping("/login")
    public class LoginController {
        @RequestMapping(value = "/setUser.do",method=RequestMethod.POST)
        public ModelAndView login(User user ,HttpServletRequest request,HttpServletResponse response ){
            HttpSession session = request.getSession(); 
            session.setAttribute("user", user);
            ModelAndView mv = new ModelAndView();
            mv.addObject("user", user);
            mv.setViewName("chat");
            return mv;
        }
        
        @RequestMapping(value = "/getLogin.do",method=RequestMethod.GET)
        public ModelAndView getlogin(){
            System.out.println("-----------------------");
            ModelAndView mv = new ModelAndView();
            mv.setViewName("login");
            return mv;
        }
    }

    前端界面有两个,分别是登录界面和聊天界面,都是用bootstrap做的

    login.jsp

    <%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
            <title>用户登录</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="renderer" content="webkit">
    
            <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
            <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
            <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
            <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
            <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
            <style type="text/css">
                body {
                    background-color:#f8f6e9;
                }
                .mycenter{
                    margin-top: 100px;
                    margin-left: auto;
                    margin-right: auto;
                    height: 350px;
                    width:500px;
                    padding: 5%;
                    padding-left: 5%;
                    padding-right: 5%;
                }
                .mycenter mysign{
                    width: 440px;
                }
                .mycenter input,checkbox,button{
                    margin-top:2%;
                    margin-left: 10%;
                    margin-right: 10%;
                }
                .mycheckbox{
                    margin-top:10px;
                    margin-left: 40px;
                    margin-bottom: 10px;
                    height: 10px;
                }
            </style>
            <script type="text/javascript">
                /* $(function(){
                    $("#submit").click(function(){
                        var data = $("#loginform").serialize();
                        $.post("${pageContext.request.contextPath}/login/setUser.do",data,function(result){
                            $("span").html(result);
                        });
                        
                    });
                    
                }); */
            
            
            
            
            </script>
            
    </head>
    <body>
    <form id="loginform" method = "post" action="${pageContext.request.contextPath}/login/setUser.do">
                <div class="mycenter">
                <div class="mysign">
                    <div class="col-lg-11 text-center text-info">
                        <h2>请登录</h2>
                    </div>
                    <div class="col-lg-10">
                        <input type="text" class="form-control" id = "username" name="username" placeholder="请输入账户名" required autofocus/>
                    </div>
                    <div class="col-lg-10"></div>
                    <div class="col-lg-10">
                        <input type="password" class="form-control" id = "password" name="password" placeholder="请输入密码" required autofocus/>
                    </div>
                    <div class="col-lg-10"></div>
                    <div class="col-lg-10 mycheckbox checkbox">
                        <input type="checkbox" class="col-lg-1">记住密码</input>
                    </div>
                    <div class="col-lg-10"></div>
                    <div class="col-lg-10">
                        <button type="submit" id = "submit" class="btn btn-success col-lg-12">登录</button>
                    </div>
                </div>
            </div>
            </form>
    </body>
    </html>

    chat.jsp

    <%@page import="com.lzl.dao.User"%>
    <%@ page language="java" contentType="text/html; charset=utf-8"
        pageEncoding="utf-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <title>聊天</title>
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <meta name="renderer" content="webkit">
    
            <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
            <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
            <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
            <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
            <script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
            <script type="text/javascript">
            var websocket;
            $(function() {
                // 首先判断是否 支持 WebSocket
                if('WebSocket' in window) {
                    websocket = new WebSocket("ws://localhost:8080/websocket2/websocket");
                } else if('MozWebSocket' in window) {
                    websocket = new MozWebSocket("ws://localhost:8080/websocket2/websocket");
                } else {
                    websocket = new SockJS("http://localhost:8080/websocket2/sockjs/websocket");
                }
                // 打开时
                websocket.onopen = function(evnt) {
                    $("#tou").html("链接服务器成功!")
                };
                // 处理消息时
                websocket.onmessage = function(evnt) {
                    console.log(evnt.data);
                    $("#msg").html($("#msg").html() + "<br/>" + evnt.data);
                };
                websocket.onerror = function(evnt) {
                    console.log("  websocket.onerror  ");
                };
                websocket.onclose = function(evnt) {
                    $("#tou").html("与服务器断开了链接!")
                };
                // 点击了发送消息按钮的响应事件
                $("#send").click(function(){
                    // 获取消息内容
                    var text = $("#message").val();
                    // 判断
                    if(text == null || text == ""){
                        alert(" content  can not empty!!");
                        return false;
                    }
                    var username = $("#username").val();
                    console.log("username="+username);
                    var msg = {
                        msgContent: text,
                        userID:username
                    };
                    // 发送消息
                    websocket.send(JSON.stringify(msg));
                    $("#message").val("");
                });
                
                $("#offline").click(function(){
                    console.log(websocket.readyState);
                    var state = websocket.readyState;
                    if(1==state){
                        websocket.close();
                        $("#offline").html("上线");
                    }else{
                        connect();
                        $("#offline").html("下线");
                    }
                });
                
                
            });
            
            
            function connect (){
                // 首先判断是否 支持 WebSocket
                if('WebSocket' in window) {
                    websocket = new WebSocket("ws://localhost:8080/websocket2/websocket");
                } else if('MozWebSocket' in window) {
                    websocket = new MozWebSocket("ws://localhost:8080/websocket2/websocket");
                } else {
                    websocket = new SockJS("http://localhost:8080/websocket2/sockjs/websocket");
                }
                // 打开时
                websocket.onopen = function(evnt) {
                    $("#tou").html("链接服务器成功!")
                };
                // 处理消息时
                websocket.onmessage = function(evnt) {
                    $("#msg").html($("#msg").html() + "<br/>" + evnt.data);
                };
                websocket.onerror = function(evnt) {
                    console.log("  websocket.onerror  ");
                };
                websocket.onclose = function(evnt) {
                    $("#tou").html("与服务器断开了链接!")
                };
            }
            
            //给发送按钮绑定回车键事件
            $(document).keydown(function(event){ 
                if(event.keyCode == 13){ //绑定回车 
                $('#send').click(); 
            } 
            }); 
            </script>
    </head>
    <body>
        <input type="hidden" value="${user.username}" id="username">
        <div>
        <div class="page-header" id="tou" style = "text-align:center">
                webSocket多终端聊天测试
            </div>
            <div class="well" id="msg" style = "800px;margin:0 auto"></div>
            &nbsp;
            <div class="col-lg">
                <div class="input-group" style = "800px;margin:0 auto">
                    <input type="text" class="form-control" placeholder="发送信息..." id="message">
                    <span class="input-group-btn">
                        <button class="btn btn-default" type="button" id="send" >发送</button>
                    </span>
                </div>
            </div>
     </div>
     &nbsp;
     <div class="container-fluid">
        <div class="row-fluid">
            <div class="span12">
                 <button class="btn btn-warning btn-block" type="button" id= "offline" style = "800px;margin:0 auto">下线</button>
            </div>
        </div>
    </div>
    </body>
    </html>

    4.效果

    这是admin这个用户的界面

    这是王小二的界面

  • 相关阅读:
    Codeforces 1485C Floor and Mod (枚举)
    CodeForces 1195D Submarine in the Rybinsk Sea (算贡献)
    CodeForces 1195C Basketball Exercise (线性DP)
    2021年初寒假训练第24场 B. 庆功会(搜索)
    任务分配(dp)
    开发工具的异常现象
    Telink MESH SDK 如何使用PWM
    Telink BLE MESH PWM波的小结
    [LeetCode] 1586. Binary Search Tree Iterator II
    [LeetCode] 1288. Remove Covered Intervals
  • 原文地址:https://www.cnblogs.com/li-zhi-long/p/9366935.html
Copyright © 2011-2022 走看看