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这个用户的界面

这是王小二的界面

原文地址:https://www.cnblogs.com/li-zhi-long/p/9366935.html