websocket协议

一、何为websocket协议

  • WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
    • 何谓全双工:全双工(Full Duplex)是通讯传输的一个术语。双方在通信时允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工指可以同时进行信号的双向传输。指A→B的同时B→A,就像是双向车道。
    • 单工就像是汽车的单行道,是在只允许甲方向乙方传送信息,而乙方不能向甲方传送 。

 参考资料:https://baike.baidu.com/item/%E5%85%A8%E5%8F%8C%E5%B 7%A5/310007?fr=aladdin

  • 在 WebSocket中,浏览器和服务器只需要完成一次握手,就可以创建持久性的连接,并进行双向数据传输。
  • 在推送功能的实现技术上,相比使用Ajax 定时轮询的方式(setInterval),WebSocket 更节省服务器资源和带宽。
  • 服务器向客户端发送数据的功能是websocket协议的典型使用场景

  image

二、websocket常用事件方法

 以下 API 用于创建 WebSocket 对象。

var Socket = new WebSocket(url, [protocol] );

 WebSocket 事件

  以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:

  image

 WebSocket 方法

  image

 三、演示WebSocket

 spring websocket实现前后端通信(服务器端)

  image  image  image  

1. 配置文件

 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>shenzhanwang</groupId>
  <artifactId>SSM</artifactId>
  <packaging>war</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>Spring-websocket</name>
  <url>http://maven.apache.org</url>
  <dependencies>
      <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>4.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>4.0.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.3.1</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.16</version>
        </dependency>
        <!-- servlet API -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1-b09</version>
            <scope>provided</scope>
        </dependency>
        <!-- MySQL驱动 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.30</version>
        </dependency>
        <!-- mchange C3P0 数据源-->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5-pre2</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!-- fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.1.29</version>
    </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <!-- spring -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <dependency>
           <groupId>org.springframework</groupId>
           <artifactId>spring-context-support</artifactId>
           <version>4.0.4.RELEASE</version>
          </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-expression</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.0.4.RELEASE</version>
        </dependency>
        <!-- aspectjweaver -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.7.2</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.quartz-scheduler</groupId>
            <artifactId>quartz-jobs</artifactId>
            <version>2.2.2</version>
        </dependency>
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>1.7.7</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.7</version>
        </dependency>
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
            <version>1.1.3</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.1</version>
        </dependency>
        <dependency>
            <groupId>ognl</groupId>
            <artifactId>ognl</artifactId>
            <version>3.0.6</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.14</version>
        </dependency>
        <dependency>
           <groupId>org.mybatis</groupId>
           <artifactId>mybatis</artifactId>
           <version>3.2.2</version>
         </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>4.1.6</version>
        </dependency>
  </dependencies>
  <build>
    <finalName>Spring-websocket</finalName>
  </build>
   <!-- 指定maven编译方式为jdk1.8版本 -->
    <profiles>
        <profile>
            <id>jdk-1.8</id>
            <activation>
                <activeByDefault>true</activeByDefault>
                <jdk>1.8</jdk>
            </activation>
            <properties>
                <maven.compiler.source>1.8</maven.compiler.source>
                <maven.compiler.target>1.8</maven.compiler.target>
                <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
            </properties>
        </profile>
    </profiles>
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
</project>
View Code

 spring-mybatis.xml

<?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:p="http://www.springframework.org/schema/p"
  xmlns:tx="http://www.springframework.org/schema/tx"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
        destroy-method="close"
        p:driverClass="com.mysql.jdbc.Driver"
        p:jdbcUrl="jdbc:mysql://192.168.240.133:3306/sakila"
        p:user="root"
        p:password="root"
        p:maxPoolSize="40"
        p:minPoolSize="2"
        p:initialPoolSize="2"
        p:maxIdleTime="30"/>
    
    <!-- mybatis文件配置,扫描所有mapper文件 -->
    <bean id="sqlSessionFactory"
        class="org.mybatis.spring.SqlSessionFactoryBean"
        p:dataSource-ref="dataSource"
        p:configLocation="classpath:conf/mybatis-config.xml"
        p:mapperLocations="classpath:mapper/*.xml"/> <!-- configLocation为mybatis属性 mapperLocations为所有mapper-->
      
    <!-- spring与mybatis整合配置,扫描所有mapper -->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"
        p:basePackage="mapper" 
        p:sqlSessionFactoryBeanName="sqlSessionFactory"/>
 
       <!-- 对数据源进行事务管理 -->
      <bean id="transactionManager" 
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
        p:dataSource-ref="dataSource"/>
        
    <tx:annotation-driven transaction-manager="transactionManager"/>
        
</beans>
View Code

 spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    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-4.0.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <context:component-scan base-package="controller,service.impl,websocket" />
    
    <!-- 处理请求时返回json字符串的中文乱码问题 -->
    <mvc:annotation-driven>
        <mvc:message-converters>
            <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="supportedMediaTypes">
                    <list>
                        <value>application/json;charset=UTF-8</value>
                    </list>
                </property>
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    
    <!-- 静态资源访问(不拦截此目录下的东西的访问) -->  
    <mvc:resources location="/js/"  mapping="/js/**" />  
    <mvc:resources location="/css/"  mapping="/css/**" />  
    
    <!--配置拦截器, 多个拦截器,顺序执行 -->  
    <mvc:interceptors>    
        <mvc:interceptor>    
            <!-- 匹配的是url路径, 如果不配置或/**,将拦截所有的Controller -->  
            <mvc:mapping path="/**" />  
            <mvc:exclude-mapping path="/login" />  
            <mvc:exclude-mapping path="/authImg"/>
            <mvc:exclude-mapping path="/loginvalidate"/>
            <bean class="interceptor.LoginIntercepter"></bean>    
        </mvc:interceptor>  
        <!-- 当设置多个拦截器时,先按顺序调用preHandle方法,然后逆序调用每个拦截器的postHandle和afterCompletion方法 -->  
    </mvc:interceptors>
    
    <!-- 页面转向解析 -->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/content/" />
        <property name="suffix" value=".jsp" />
    </bean>    
    
    <!-- 文件上传 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"></bean>
</beans>
View Code

 mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration 
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <plugins>
    <!-- com.github.pagehelper为PageHelper类所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageHelper">
        <!-- 4.0.0以后版本可以不设置该参数 -->
        <property name="dialect" value="mysql"/>
        <!-- 该参数默认为false -->
        <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
        <!-- 和startPage中的pageNum效果一样-->
        <property name="offsetAsPageNum" value="true"/>
        <!-- 该参数默认为false -->
        <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
        <property name="rowBoundsWithCount" value="true"/>
        <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
        <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
        <property name="pageSizeZero" value="true"/>
        <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
        <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
        <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
        <property name="reasonable" value="false"/>
        <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
        <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
        <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
        <!-- 不理解该含义的前提下,不要随便复制该配置 -->
        <property name="params" value="pageNum=pageHelperStart;pageSize=pageHelperRows;"/>
        <!-- 支持通过Mapper接口参数来传递分页参数 -->
        <property name="supportMethodsArguments" value="false"/>
        <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
        <property name="returnPageInfo" value="none"/>
    </plugin>
</plugins>
</configuration>
View Code

 log4j.properties

 ### set log levels ###
#log4j.rootLogger = debug , stdout , D , E
log4j.rootLogger = info , stdout , D

###  output to the console ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
#log4j.appender.stdout.layout.ConversionPattern = %d{ABSOLUTE} %5p %c{ 1 }:%L - %m%n
log4j.appender.stdout.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [%c]-[%p] %m%n

### Output to the log file ###
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = ${webapp.root}/WEB-INF/logs/error.log 
log4j.appender.D.Append = true
log4j.appender.D.Threshold = ERROR 
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
View Code

 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
  <welcome-file-list>
    <welcome-file>/content/login.jsp</welcome-file>
  </welcome-file-list>
    <!-- 读取spring配置文件 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:conf/spring-mybatis.xml</param-value>
    </context-param>
    <!-- Spring字符集过滤器 -->
    <filter>
        <filter-name>SpringEncodingFilter</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>SpringEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- 日志记录 -->
    <context-param>
        <!-- 日志配置文件路径 -->
        <param-name>log4jConfigLocation</param-name>
        <param-value>classpath:conf/log4j.properties</param-value>
    </context-param>
    <context-param>
        <!-- 日志页面的刷新间隔 -->
        <param-name>log4jRefreshInterval</param-name>
        <param-value>6000</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>
    <!-- 启动spring -->
    <listener>
           <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
      <!-- spring mvc配置 -->
      <servlet>
        <servlet-name>sakila</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:conf/spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>sakila</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
</web-app>
View Code

2. websocket连接相关类

 HandShake

package websocket;

import java.util.Map;

import javax.servlet.http.HttpSession;

import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;


/**
 * Socket建立连接(握手)和断开
 * 握手拦截器类:创建websocket连接时的拦截器,记录建立连接的用户的session以便根据不同session来通信
 */
public class HandShake implements HandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        System.out.println("Websocket:用户[ID:" + ((ServletServerHttpRequest) request).getServletRequest().getSession(false).getAttribute("uid") + "]已经建立连接");
        //在握手之前将HttpSession中的用户,copy放到WebSocket Session中
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpSession session = servletRequest.getServletRequest().getSession(false);
            // 标记用户
            Long uid = (Long) session.getAttribute("uid");
            if(uid!=null){
                attributes.put("uid", uid);
            }else{
                return false;
            }
        }
        return true;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        System.out.println("after hand");
    }

}
View Code

 MyWebSocketHandler

package websocket;

import java.io.IOException;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
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 po.Message;
import service.LoginService;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;

/**
 * Socket处理器
 * 消息处理类
 */
@Component
public class MyWebSocketHandler implements WebSocketHandler {
    //用于保存HttpSession与WebSocketSession的映射关系
    public static final Map<Long, WebSocketSession> userSocketSessionMap;

    @Autowired
    LoginService loginservice;
    
    static {
        userSocketSessionMap = new ConcurrentHashMap<Long, WebSocketSession>();
    }
    
    /**
     * 建立连接后,把登录用户的id写入WebSocketSession
     */
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        //从 WebSocket Session中取得用户
        Long uid = (Long) session.getAttributes().get("uid");
        String username=loginservice.getnamebyid(uid);
        if (userSocketSessionMap.get(uid) == null) {
            userSocketSessionMap.put(uid, session);
            Message msg = new Message();
            msg.setFrom(0L);//0表示上线消息
            msg.setText(username);
            this.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
        }
    }

    /**
     * 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理
     */
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
            if(message.getPayloadLength()==0)
                return;
            Message msg=new Gson().fromJson(message.getPayload().toString(),Message.class);
            msg.setDate(new Date());
            sendMessageToUser(msg.getTo(), new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
    }

    /**
     * 消息传输错误处理
     */
    public void handleTransportError(WebSocketSession session,
            Throwable exception) throws Exception {
        if (session.isOpen()) {
            session.close();
        }
        Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
        // 移除当前抛出异常用户的Socket会话
        while (it.hasNext()) {
            Entry<Long, WebSocketSession> entry = it.next();
            if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
                String username=loginservice.getnamebyid(entry.getKey());
                Message msg = new Message();
                msg.setFrom(-2L);
                msg.setText(username);
                this.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
                break;
            }
        }
    }

    /**
     * 关闭连接后
     */
    public void afterConnectionClosed(WebSocketSession session,CloseStatus closeStatus) throws Exception {
        System.out.println("Websocket:" + session.getId() + "已经关闭");
        Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();
        // 移除当前用户的Socket会话
        while (it.hasNext()) {
            Entry<Long, WebSocketSession> entry = it.next();
            if (entry.getValue().getId().equals(session.getId())) {
                userSocketSessionMap.remove(entry.getKey());
                System.out.println("Socket会话已经移除:用户ID" + entry.getKey());
                String username=loginservice.getnamebyid(entry.getKey());
                Message msg = new Message();
                msg.setFrom(-2L);//下线消息,用-2表示
                msg.setText(username);
                this.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
                break;
            }
        }
    }

    public boolean supportsPartialMessages() {
        return false;
    }

    /**
     * 给所有在线用户发送消息
     * @param message
     * @throws IOException
     */
    public void broadcast(final TextMessage message) throws IOException {
        Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();

        //多线程群发
        while (it.hasNext()) {

            final Entry<Long, WebSocketSession> entry = it.next();

            if (entry.getValue().isOpen()) {
                // entry.getValue().sendMessage(message);
                new Thread(new Runnable() {

                    public void run() {
                        try {
                            if (entry.getValue().isOpen()) {
                                entry.getValue().sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                }).start();
            }

        }
    }

    /**
     * 给某个用户发送消息
     * 
     * @param uid
     * @param message
     * @throws IOException
     */
    public void sendMessageToUser(Long uid, TextMessage message) throws IOException {
        WebSocketSession session = userSocketSessionMap.get(uid);
        if (session != null && session.isOpen()) {
            //输出消息到客户端
            session.sendMessage(message);
        }
    }

}
View Code

 WebSocketConfig

package websocket;

import javax.annotation.Resource;
import org.springframework.stereotype.Component;
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;

/**
 * WebScoket配置处理器
 */
@Component
//声明该类支持WebSocket
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {

    @Resource
    MyWebSocketHandler handler;

    /**
         websocket入口,允许访问的域、注册Handler、SockJs支持和拦截器。
             registerWebSocketHandlers方法是向spring容器注册一个handler地址,我把他理解成requestMapping
            addInterceptors:拦截器,当建立websocket连接的时候,我们可以通过继承spring的HttpSessionHandshakeInterceptor来搞事情
            withSockJS: 这个是应对浏览器不支持websocket协议的时候降级为轮询的处理
             setAllowedOrigins:跨域设置,*表示所有域名都可以,不限制, 域包括ip:port, 指定*可以是任意的域名,不加的话默认localhost+本服务端口
     */
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(handler, "/ws").addInterceptors(new HandShake());
        registry.addHandler(handler, "/ws/sockjs").addInterceptors(new HandShake()).withSockJS();
        //setAllowedOrigins()方法的支持spring4.1.5之后的版本才支持
        //setAllowedOrigins(String... val),允许指定的域名或IP(含端口号)建立长连接,可以只设置允许自家域名访问,如果不限时使用"*"号,如果指定了域名,则必须要以http或https开头。
    }

}
View Code

 Message

package po;

import java.util.Date;

public class Message {

    //发送者
    public Long from;
    //发送者名称
    public String fromName;
    //接收者
    public Long to;
    //发送的文本
    public String text;
    //发送日期
    public Date date;

    public Long getFrom() {
        return from;
    }

    public void setFrom(Long from) {
        this.from = from;
    }

    public Long getTo() {
        return to;
    }

    public void setTo(Long to) {
        this.to = to;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public String getFromName() {
        return fromName;
    }

    public void setFromName(String fromName) {
        this.fromName = fromName;
    }

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }

}
View Code

3. 登录聊天室

 WEB-INF/content/login.jsp登录页面

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>login</title>
    <script src="./js/jquery-1.12.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<style type="text/css">
.vertical-center{
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
</style>
  </head>
  <body>
    <div class="container vertical-center">
      <div class="col-md-6 col-md-offset-3">
        <form  action="loginvalidate" method="post">
          <h2 >登录聊天室</h2>
          <label for="inputEmail" class="sr-only">userid</label>
          <input type="text" name="username" id="inputEmail" class="form-control" placeholder="userid" required autofocus>
          <label for="inputPassword" class="sr-only">Password</label>
          <input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
          <label for="inputEmail" class="sr-only">userid</label>
          <input type="text" name="pic" id="pic" class="form-control" placeholder="验证码" required>              
          验证码:<img src="authImg" width="120" height="40">
          <button class="btn btn-lg btn-primary btn-block" type="submit">login</button>
        </form>
      </div>
    </div> <!-- /container -->
  </body>
</html>
View Code

 WEB-INF/content/failcode.jsp验证码错误页面

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<script>
alert("验证码错误");
window.history.go(-1);
</script>
View Code

 WEB-INF/content/chatroom.jsp登录成功后跳转页面

        //连接建立时触发
        websocket.onopen = function(event) {
            // debugger
            // alert('连接成功!');
            console.log("连接成功!");
        };

        $.post("onlineusers",function(data){
            console.log("onlineusers!!!");
            for(var i=0;i<data.length;i++)
                $("#users").append('<a href="#" onclick="talk(this)" class="list-group-item">'+data[i]+'</a>');
        });
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <title>聊天室</title>
    <script src="./js/jquery-1.12.3.min.js"></script>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="//cdn.bootcss.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<style>
body{
    margin-top:5px;
}
</style>
</head>
  <body>
    <div class="container">
        <div class="row">
            <div class="col-md-3">
            <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">当前登录用户</h3>
                  </div>
                  <div class="panel-body">
                    <div class="list-group">
                     <a href="#" class="list-group-item">你好,${sessionScope.username}</a>
                     <a href="logout" class="list-group-item">退出</a>
                    </div>
                  </div>
                </div>
                <div class="panel panel-primary" id="online">
                  <div class="panel-heading">
                    <h3 class="panel-title">当前在线的其他用户</h3>
                  </div>
                  <div class="panel-body">
                    <div class="list-group" id="users">
                    </div>
                  </div>
                </div>
                <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">群发系统广播</h3>
                  </div>
                  <div class="panel-body">
                    <input type="text" class="form-control"  id="msg" /><br>
                    <button id="broadcast" type="button" class="btn btn-primary">发送</button>
                  </div>
                </div>
            </div>
              <div class="col-md-9">
                  <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title" id="talktitle"></h3>
                  </div>
                  <div class="panel-body">
                    <div class="well" id="log-container" style="height:400px;overflow-y:scroll">
                    
                    </div>
                        <input type="text" id="myinfo" class="form-control col-md-12" /> <br>
                        <button id="send" type="button" class="btn btn-primary">发送</button>
                    </div>
                </div>
              </div>
        </div>
    </div> 
<script>
    $(document).ready(function() {
        // 指定websocket路径
        var websocket;
        if ('WebSocket' in window) {
            websocket = new WebSocket("ws://localhost:8080/Spring-websocket/ws?uid="+${sessionScope.uid});
        } else if ('MozWebSocket' in window) {
            websocket = new MozWebSocket("ws://localhost:8080/Spring-websocket/ws"+${sessionScope.uid});
        } else {
            websocket = new SockJS("http://localhost:8080/Spring-websocket/ws/sockjs"+${sessionScope.uid});
        }
        //var websocket = new WebSocket('ws://localhost:8080/Spring-websocket/ws');
        websocket.onmessage = function(event) {
            var data=JSON.parse(event.data);
                if(data.from>0||data.from==-1){//用户或者群消息
            // 接收服务端的实时消息并添加到HTML页面中
            $("#log-container").append("<div class='bg-info'><label class='text-danger'>"+data.fromName+"&nbsp;"+data.date+"</label><div class='text-success'>"+data.text+"</div></div><br>");
            // 滚动条滚动到最低部
            scrollToBottom();
            }else if(data.from==0){//上线消息
                if(data.text!="${sessionScope.username}")
                {    
                    $("#users").append('<a href="#" onclick="talk(this)" class="list-group-item">'+data.text+'</a>');
                    //alert(data.text+"上线了");
                }
            }else if(data.from==-2){//下线消息
                if(data.text!="${sessionScope.username}")
                {    
                    $("#users > a").remove(":contains('"+data.text+"')");
                    //alert(data.text+"下线了");
                }
            }
        };
        websocket.onopen = function(event) {
            // debugger
            alert('连接成功!');
        };
        websocket.onclose = function(event) {
            debugger
            //alert('连接关闭!');
        };
        websocket.onerror = function(event) {
            //alert('连接出错!');
        };
        $.post("onlineusers",function(data){
            for(var i=0;i<data.length;i++)
                $("#users").append('<a href="#" onclick="talk(this)" class="list-group-item">'+data[i]+'</a>');
        });
        
        $("#broadcast").click(function(){
            $.post("broadcast",{"text":$("#msg").val()});
        });
        
        $("#send").click(function(){
            $.post("getuid",{"username":$("body").data("to")},function(d){
                var v=$("#myinfo").val();
                
                if(v==""){
                    return;
                }else{
                    var data={};
                    data["from"]="${sessionScope.uid}";
                    data["fromName"]="${sessionScope.username}";
                    data["to"]=d.uid;
                    data["text"]=v;
                    websocket.send(JSON.stringify(data));
                    $("#log-container").append("<div class='bg-success'><label class='text-info'>我&nbsp;"+new Date()+"</label><div class='text-info'>"+v+"</div></div><br>");
                    scrollToBottom();
                    $("#myinfo").val("");
                }
            });
            
        });
        
    });
   
   function talk(a){
       $("#talktitle").text(""+a.innerHTML+"的聊天");
       $("body").data("to",a.innerHTML);
   }
   function scrollToBottom(){
        var div = document.getElementById('log-container');
        div.scrollTop = div.scrollHeight;
    }
</script>    
    
  </body>
</html>
View Code

 WEB-INF/content/fail.jsp登录失败页面

<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<script>
alert("密码错误");
window.history.go(-1);
</script>
View Code

 LoginIntercepter登录拦截器类

package interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class LoginIntercepter extends HandlerInterceptorAdapter{
    /**  
     * 在业务处理器处理请求之前被调用  
     * 如果返回false  
     *     从当前的拦截器往回执行所有拦截器的afterCompletion(),再退出拦截器链 
     * 如果返回true  
     *    执行下一个拦截器,直到所有的拦截器都执行完毕  
     *    再执行被拦截的Controller  
     *    然后进入拦截器链,  
     *    从最后一个拦截器往回执行所有的postHandle()  
     *    接着再从最后一个拦截器往回执行所有的afterCompletion()  
     */    
    @Override    
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {    
        String username =  (String)request.getSession().getAttribute("username");   
        if(username == null){  
            request.getRequestDispatcher("/WEB-INF/content/login.jsp").forward(request, response);  
            return false;  
        }else  
            return true;     
    }    
    
    /** 
     * 在业务处理器处理请求执行完成后,生成视图之前执行的动作    
     * 可在modelAndView中加入数据,比如当前时间 
     */  
    @Override    
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {     
    }    
    
    /**  
     * 在DispatcherServlet完全处理完请求后被调用,可用于清理资源等   
     *   
     * 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion()  
     */    
    @Override    
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {    
    }    
  
}
View Code

 导入sakila库staff表数据

  image

 Staff实体类

package po;

public class Staff {
    private byte staff_id;
    private String first_name;
    private String last_name;
    private short address_id;
    private String email;
    private String username;
    private String password;
    private String last_update;
    
    public String getLast_update() {
        return last_update;
    }
    public byte getStaff_id() {
        return staff_id;
    }
    public void setStaff_id(byte staff_id) {
        this.staff_id = staff_id;
    }
    public void setLast_update(String last_update) {
        this.last_update = last_update;
    }
    public String getFirst_name() {
        return first_name;
    }
    public void setFirst_name(String first_name) {
        this.first_name = first_name;
    }
    public String getLast_name() {
        return last_name;
    }
    public void setLast_name(String last_name) {
        this.last_name = last_name;
    }
    
    public short getAddress_id() {
        return address_id;
    }
    public void setAddress_id(short address_id) {
        this.address_id = address_id;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
}
View Code

 AuthImg类封装登录验证码图片

package web;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.io.*;
import java.awt.*;
import java.awt.image.*;
import java.util.*;
import javax.imageio.*;

@WebServlet(urlPatterns={"/WEB-INF/content/authImg.jsp"})
public class AuthImg extends HttpServlet
{
    private final Font mFont =
        new Font("Arial Black", Font.PLAIN, 16);
    private final int IMG_WIDTH = 100;
    private final int IMG_HEIGTH = 18;
    private Color getRandColor(int fc,int bc)
    {
        Random random = new Random();
        if(fc > 255) fc = 255;
        if(bc > 255) bc=255;
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r , g , b);
    }
    public void service(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException
    {
        response.setHeader("Pragma","No-cache");
        response.setHeader("Cache-Control","no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/jpeg");
        BufferedImage image = new BufferedImage
            (IMG_WIDTH , IMG_HEIGTH , BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();
        Random random = new Random();
        g.setColor(getRandColor(200 , 250));
        g.fillRect(1, 1, IMG_WIDTH - 1, IMG_HEIGTH - 1);
        g.setColor(new Color(102 , 102 , 102));
        g.drawRect(0, 0, IMG_WIDTH - 1, IMG_HEIGTH - 1);
        g.setColor(getRandColor(160,200));
        for (int i = 0 ; i < 30 ; i++)
        {
            int x = random.nextInt(IMG_WIDTH - 1);
            int y = random.nextInt(IMG_HEIGTH - 1);
            int xl = random.nextInt(6) + 1;
            int yl = random.nextInt(12) + 1;
            g.drawLine(x , y , x + xl , y + yl);
        }
        g.setColor(getRandColor(160,200));
        for (int i = 0 ; i < 30 ; i++)
        {
            int x = random.nextInt(IMG_WIDTH - 1);
            int y = random.nextInt(IMG_HEIGTH - 1);
            int xl = random.nextInt(12) + 1;
            int yl = random.nextInt(6) + 1;
            g.drawLine(x , y , x - xl , y - yl);
        }
        g.setFont(mFont);
        String sRand = "";
        for (int i = 0 ; i < 4 ; i++)
        {
            String tmp = getRandomChar();
            sRand += tmp;
            g.setColor(new Color(20 + random.nextInt(110)
                ,20 + random.nextInt(110)
                ,20 + random.nextInt(110)));
            g.drawString(tmp , 15 * i + 10,15);
        }
        HttpSession session = request.getSession(true);
        session.setAttribute("rand" , sRand);
//        System.out.println("写入session"+sRand);
        g.dispose();
        ImageIO.write(image, "JPEG", response.getOutputStream());
    }
    private String getRandomChar()
    {
        int rand = (int)Math.round(Math.random() * 2);
        long itmp = 0;
        char ctmp = '\u0000';
        switch (rand)
        {
            case 1:
                itmp = Math.round(Math.random() * 25 + 65);
                ctmp = (char)itmp;
                return String.valueOf(ctmp);
            case 2:
                itmp = Math.round(Math.random() * 25 + 97);
                ctmp = (char)itmp;
                return String.valueOf(ctmp);
            default :
                itmp = Math.round(Math.random() * 9);
                return  itmp + "";
        }
    }
}
View Code

 controller/Pic类映射验证码请求

package controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class Pic {
@RequestMapping(value="/authImg")
public String getpic(){
    return "authImg";
}
}
View Code

 controller/Login类封装登录相关方法

package controller;

import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import service.LoginService;

@Controller
public class Login {
    @Autowired
    LoginService loginservice;
    
    @RequestMapping("/loginvalidate")
    public String loginvalidate(@RequestParam("username") String username,@RequestParam("pic") String pic,@RequestParam("password") String pwd,HttpSession httpSession){
        String picode=(String) httpSession.getAttribute("rand");
        if(!picode.equalsIgnoreCase(pic))
            return "failcode";
        if(username==null)
            return "login";
        String realpwd=loginservice.getpwdbyname(username);
        if(realpwd!=null&&pwd.equals(realpwd))
        {
            long uid=loginservice.getUidbyname(username);
            httpSession.setAttribute("username", username);
            httpSession.setAttribute("uid", uid);
            return "chatroom";
        }else
            return "fail";
    }
    
    @RequestMapping("/login")
    public String login(){
        return "login";
    }
    
    @RequestMapping("/logout")
    public String logout(HttpSession httpSession){
        httpSession.removeAttribute("username");
        httpSession.removeAttribute("uid");
        return "login";
    }
  }

 ChatController类

package controller;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.socket.WebSocketSession;
import service.LoginService;
import websocket.MyWebSocketHandler;

@Controller
public class ChatController {

    @Autowired
    LoginService loginservice;
    
    @RequestMapping("/onlineusers")
    @ResponseBody
    public Set<String> onlineusers(HttpSession session){
        Map<Long, WebSocketSession> map=MyWebSocketHandler.userSocketSessionMap;
        Set<Long> set=map.keySet();
        Iterator<Long> it = set.iterator();
        Set<String> nameset=new HashSet<String>();
        while(it.hasNext()){
            Long entry = it.next();
            String name=loginservice.getnamebyid(entry);
            String user=(String)session.getAttribute("username");
            if(!user.equals(name))
                nameset.add(name);
        }
        return nameset;
    }
}

 LoginService类

package service;

public interface LoginService {
    String getpwdbyname(String name);
    Long getUidbyname(String name);
    String getnamebyid(long id);
}

 LoginServiceImpl类

package service.impl;
import mapper.LoginMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import po.Staff;
import service.LoginService;
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,timeout=5)
@Service("loginservice")
public class LoginServiceImpl implements LoginService{
    @Autowired
    LoginMapper loginmapper;
    public String getpwdbyname(String name) {
        Staff s=loginmapper.getpwdbyname(name);
        if(s!=null)
            return s.getPassword();
        else
            return null;
    }
    public Long getUidbyname(String name) {
        Staff s=loginmapper.getpwdbyname(name);
        if(s!=null)
            return (long) s.getStaff_id();
        else
            return null;
    }
    public String getnamebyid(long id) {
        Staff s=loginmapper.getnamebyid(id);
        if(s!=null)
            return s.getUsername();
        else
            return null;
    }
}

 LoginMapper类

package mapper;
import po.Staff;

public interface LoginMapper {
    Staff getpwdbyname(String name);
    Staff getnamebyid(long id);
}

 resources/mapper/LoginMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.LoginMapper">
    <resultMap type="po.Staff" id="staffMap">
        <id column="staff_id" property="staff_id" ></id>
        <result column="username" property="username"/>
        <result column="password" property="password"/>
        <result column="last_name" property="last_name"/>
    </resultMap>
    <select id="getpwdbyname" parameterType="String"  resultMap="staffMap">
        select * from staff where binary username=#{name}
    </select>
    <select id="getnamebyid" parameterType="long"  resultMap="staffMap">
        select * from staff where staff_id=#{id}
    </select>
</mapper>

 启动tomcat(local),虚拟目录配置/Spring-websocket,浏览器访问http://localhost:8080/Spring-websocket/login

  image

 使用staff表的用户名和密码登录,输入错误的验证码,输入错误的密码

   image  image

  登录成功,检查页面展示

  image

 4. 群发系统广播

 4.1 聊天室页面输入群发消息,发送按钮绑定点击事件

                <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title">群发系统广播</h3>
                  </div>
                  <div class="panel-body">
                    <input type="text" class="form-control"  id="msg" /><br>
                    <button id="broadcast" type="button" class="btn btn-primary">发送</button>
                  </div>
                </div>
        $("#broadcast").click(function(){
            $.post("broadcast",{"text":$("#msg").val()});
        });

 4.2 Controller

  执行controller之前会先调用LoginIntercepter拦截器的preHandle方法,然后调用controller中的broadcast方法

    @Autowired
    MyWebSocketHandler handler;
    
    // 发布系统广播(群发)
    @ResponseBody
    @RequestMapping(value = "broadcast", method = RequestMethod.POST)
    public void broadcast(@RequestParam("text") String text) throws IOException {
        Message msg = new Message();
        msg.setDate(new Date());
        msg.setFrom(-1L);//-1表示系统广播
        msg.setFromName("系统广播");
        msg.setTo(0L);
        msg.setText(text);
        handler.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
    }

 4.3 MyWebSocketHandler类

    /**
     * 给所有在线用户发送消息
     * @param message
     * @throws IOException
     */
    public void broadcast(final TextMessage message) throws IOException {
        Iterator<Entry<Long, WebSocketSession>> it = userSocketSessionMap.entrySet().iterator();

        //多线程群发
        while (it.hasNext()) {
            final Entry<Long, WebSocketSession> entry = it.next();
            if (entry.getValue().isOpen()) {
                // entry.getValue().sendMessage(message);
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            if (entry.getValue().isOpen()) {
                                entry.getValue().sendMessage(message);
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        }
    }

 浏览器发送群发消息,其他用户登录成功可以看到这条消息

  image image

5. 发送消息给一个用户

 5.1 给发送绑定点击事件chatroom.jsp

                  <div class="panel panel-primary">
                  <div class="panel-heading">
                    <h3 class="panel-title" id="talktitle"></h3>
                  </div>
                  <div class="panel-body">
                    <div class="well" id="log-container" style="height:400px;overflow-y:scroll">
                    </div>
                        <input type="text" id="myinfo" class="form-control col-md-12" /> <br>
                        <button id="send" type="button" class="btn btn-primary">发送</button>
                    </div>
                </div>
        $("#send").click(function(){
            $.post("getuid",{"username":$("body").data("to")},function(d){
                var v=$("#myinfo").val();
                
                if(v==""){
                    return;
                }else{
                    var data={};
                    data["from"]="${sessionScope.uid}";
                    data["fromName"]="${sessionScope.username}";
                    data["to"]=d.uid;
                    data["text"]=v;
                    websocket.send(JSON.stringify(data));
                    $("#log-container").append("<div class='bg-success'><label class='text-info'>&nbsp;"+new Date()+"</label><div class='text-info'>"+v+"</div></div><br>");
                    scrollToBottom();
                    $("#myinfo").val("");
                }
            });
        });

 5.2 User实体类

package po;

public class User {
    Long uid;
    public Long getUid() {
        return uid;
    }
    public void setUid(Long uid) {
        this.uid = uid;
    }
}

 5.3 Controller

    @Autowired
    LoginService loginservice;

    @RequestMapping("getuid")
    @ResponseBody
    public User getuid(@RequestParam("username")String username){
        Long a=loginservice.getUidbyname(username);
        User user=new User();
        user.setUid(a);
        return user;
    }

 浏览器登录成功,点击“当前在线的其他用户”中的用户名,右侧输入信息内容,发送

  image  image

  image

 

posted on 2025-12-03 22:22  花溪月影  阅读(16)  评论(0)    收藏  举报