Spring+Tomcat+WebSocket教程 附源码

前言

我们知道HTTP协议是无状态、无连接的,采用的是请求/响应模式,通信请求只能由客户端发起,服务器响应。这种请求/响应模式在客户端服务器需要持续的交互时候就显得很鸡肋,在HMTL5出来之前,要实现客户端服务器持续交互大多数都是通过AJAX轮询,但是轮询效率低,浪费带宽和服务器资源。因此WebSocket就发明出来了,WebSocket是HTML5提供的一种在单个TCP连接上进行全双工通信的协议。接下来我运用Spring和WebSocket实现一个简单的聊天功能,希望能对大家有帮助。

项目目录

image

添加依赖包

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>nChat</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--Sping核心依赖-->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-core -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context-support -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.3.RELEASE</version>
            <scope>test</scope>
        </dependency>

        <!--Mybatis依赖-->
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.4.6</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-messaging -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-messaging</artifactId>
            <version>5.1.3.RELEASE</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.springframework/spring-websocket -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-websocket</artifactId>
            <version>5.1.2.RELEASE</version>
        </dependency>

        <!--MySQL连接驱动-->
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

</project>

WebSocket实现

Java实现WebSocket的方式很多,不同厂商实现WebSocket的方式大径相同。

Spring实现WebSocket

Spring实现WebSocket,需要先添加Spring的对WebSocket支持的依赖

<!-- https://mvnrepository.com/artifact/org.springframework/spring-messaging -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-messaging</artifactId>
    <version>5.1.3.RELEASE</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework/spring-websocket -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-websocket</artifactId>
    <version>5.1.2.RELEASE</version>
</dependency>

在Java中导入Spring WebSocket的包import org.springframework.web.socket.*;,Spring实现WebSocket需要编写以下几项。

  1. 配置WebSocket
    配置WebSocket的方式有2中,一种是编写配置类,另一种是编写配置文件(XML文件),配置WebSocket的作用是将WebSocket处理器、拦截器添加到注册中心,这里我使用的是配置类来配置。
    WebSocketConfig.java
package com.nChat.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebMvc
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        //这个网址是用于websocket连接的建立 通信用的
        webSocketHandlerRegistry
                .addHandler(new WebSocketHandler(), "/ws/socketServer")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*");
    }
    
}

三个注解的作用如下

  • @Configuration注解:声明这个类为配置类(相当于web.xml配置文件中的)配置Spring容器应用上下文,即项目启动的时候会加载这个配置类。
  • @EnableWebMvc注解:开启Spring MVC,不加这个的话,在Controller的RequestMapping就失效,我也不知道为啥。
  • @EnableWebSocket注解:开启WebSocket服务。
    registerWebSocketHandlers方法配置WebSocket入口、允许访问的域,注册WebSocket处理器、拦截器等,当请求访问/ws/socketServer的时候,就会建立起WebSocket连接。
  1. 编写处理器
    WebSocketHandler.java
package com.nChat.websocket;

import org.springframework.stereotype.Service;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;


@Service
public class WebSocketHandler extends TextWebSocketHandler {

    public static final Map<Integer,WebSocketSession> USER_SOCKET_SESSION_MAP;
    static{
            USER_SOCKET_SESSION_MAP = new HashMap<Integer, WebSocketSession>();
    }

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        int uid = Integer.parseInt(session.getAttributes().get("WEBSOCKET_UID").toString());
        //如果是新的用户连接 则将session保存在USER_SOCKET_SESSION_MAP中
        if (USER_SOCKET_SESSION_MAP.get(uid) == null || !USER_SOCKET_SESSION_MAP.get(uid).isOpen()) {
            USER_SOCKET_SESSION_MAP.put(uid, session);
        }
        super.afterConnectionEstablished(session);
    }

    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
        super.handleMessage(session, message);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        super.handleTextMessage(session, message);
    }

    @Override
    protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
        super.handlePongMessage(session, message);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        super.handleTransportError(session, exception);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        super.afterConnectionClosed(session, status);
    }

    @Override
    public boolean supportsPartialMessages() {
        return super.supportsPartialMessages();
    }

    /**
     * @description: 给指定用户发送信息
     * @param: [uid, message]
     * @return: void
     * @author: Xue 8
     * @date: 2019/1/19
     */
    public void sendMessageToUser(int uid, TextMessage message){
        WebSocketSession session = USER_SOCKET_SESSION_MAP.get(uid);
        if (session != null && session.isOpen()) {
            try {
                session.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

WebSocket处理器继承TextWebSocketHandler(或BinaryWebSocketHandler),在这里重写相应的方法和编写自己的业务代码,Spring在收到WebSocket事件时,就会调用相事件相应的方法,这里我自定义了一个发送信息给指定用户的方法sendMessageToUserWebSocketSession是WebSocket的抽象,WebSocketSession就像是连接服务器和客户端之间的一条专属通道,一个WebSocketSession对应一个用户,WebSocket的操作都是基于这个WebSocketSession进行的。

  1. 编写拦截器
    WebSocketInterceptor.java
package com.nChat.websocket;

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.support.HttpSessionHandshakeInterceptor;

import java.util.Collection;
import java.util.Map;

public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor {
    public WebSocketInterceptor() {
        super();
    }

    public WebSocketInterceptor(Collection<String> attributeNames) {
        super(attributeNames);
    }

    @Override
    public Collection<String> getAttributeNames() {
        return super.getAttributeNames();
    }

    @Override
    public void setCopyAllAttributes(boolean copyAllAttributes) {
        super.setCopyAllAttributes(copyAllAttributes);
    }

    @Override
    public boolean isCopyAllAttributes() {
        return super.isCopyAllAttributes();
    }

    @Override
    public void setCopyHttpSessionId(boolean copyHttpSessionId) {
        super.setCopyHttpSessionId(copyHttpSessionId);
    }

    @Override
    public boolean isCopyHttpSessionId() {
        return super.isCopyHttpSessionId();
    }

    @Override
    public void setCreateSession(boolean createSession) {
        super.setCreateSession(createSession);
    }

    @Override
    public boolean isCreateSession() {
        return super.isCreateSession();
    }

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
        int uid = Integer.parseInt(servletServerHttpRequest.getServletRequest().getParameter("uid"));
        System.out.println("coming " + uid);
        if (uid != 0) {
            //在这里拦截请求 在捂手前将uid保存到WebSocketSession中 让处理器WebSocketHandler根据这个uid进行操作
            attributes.put("WEBSOCKET_UID", uid);
        }
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }

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

WebSocket拦截器继承HttpSessionHandshakeInterceptor,在握手前后对请求进行拦截,在握手前将请求拦截,也就是当请求访问/ws/socketServer的时候,会对请求拦截,可以获取到请求中的URL参数、请求头、协议等信息,然后将这些信息保存在WebSocketSession中,将用户和WebSocketSession关联起来。

  1. 编写Spring MVC控制器
    IndexController.java
package com.nChat.controller;

import com.nChat.websocket.WebSocketHandler;
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 org.springframework.web.socket.TextMessage;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@Controller
public class IndexController {

    @Autowired
    WebSocketHandler webSocketHandler;

    @RequestMapping("/send")
    public String send(HttpServletRequest request,
                        HttpServletResponse response){
        return "send";
    }

    @RequestMapping("/doSend")
    public String doSend(HttpServletRequest request,
                       HttpServletResponse response,
                       @RequestParam(value = "uid") int uid,
                       @RequestParam(value = "messages") String messages){

        HttpSession session = request.getSession(true);
        session.setAttribute("SESSION_USERNAME", uid);
        webSocketHandler.sendMessageToUser(uid,new TextMessage(messages));
        return "send";
    }

    @RequestMapping("/register")
    public String register(HttpServletRequest request,
                        HttpServletResponse response){

        return "register";
    }

}

注意这里的@RequestMapping和WebSocket配置类中的/ws/socketServer区别,配置类中的/ws/socketServer是用于客户端和服务器建立WebSocket连接用的,而Controller的@RequestMapping是用于处理客户端请求用的。

  1. 编写前端 测试WebSocket的建立和发信息
    用于新建WebSocket连接,其中用UID来表示WebSocket连接
    register.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>register</title>
</head>
<body>
<script type="text/javascript" src="http://cdn.bootcss.com/jquery/3.1.0/jquery.min.js"></script>
<script type="text/javascript" src="http://cdn.bootcss.com/sockjs-client/1.1.1/sockjs.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/reconnecting-websocket/1.0.0/reconnecting-websocket.js"></script>
<script type="text/javascript">
    var websocket = null;
    function createWebSocket() {
        if ('WebSocket' in window) {
            websocket = new WebSocket("ws://localhost:8080/ws/socketServer?uid=" + $("#uid").val());
            console.log($("#uid").val())
        }
        else if ('MozWebSocket' in window) {
            websocket = new MozWebSocket("ws://localhost:8080/ws/socketServer?uid=" + $("#uid").val());
        }
        else {
            websocket = new SockJS("http://localhost:8080/ws/socketServer?uid=" + $("#uid").val());
        }

        websocket.onopen = onOpen;
        websocket.onmessage = onMessage;
        websocket.onerror = onError;
        websocket.onclose = onClose;

        function onOpen(openEvt) {
            //alert(openEvt.Data);
        }

        function onMessage(evt) {
            alert(evt.data);
        }
        function onError() {

        }
        function onClose() {

        }

        window.close=function()
        {
            websocket.onclose();
        }

    }

</script>
请输入UID:<input rows="5" cols="10" id="uid" name="uid"></input>
<button onclick="createWebSocket();">建立WS连接</button>
</body>
</html>

用于发信息的页面,根据UID进行信息的发送
send.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>
<body>
<h2>send messages</h2>
<body>

<form action="/doSend">
  发送给谁:<input type="text" name="uid"/>
  发送什么信息:<input type="text" name="messages"/>
  <input type="submit" value="发送"/>
</form>

</body>
</body>
</html>
  1. 运行测试
    分别建立UID为1、2的WebSocket连接。
    image
    image

给UID为1的WebSocket发送信息
image
image

给UID为2的WebSocket发送信息
image
image

Tomcat实现WebSocket

使用Tomcat实现WebSocket没有像Spring实现WebSocket那样繁琐,只需要编写一个处理器即可。
首先添加依赖

<!-- https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api -->
<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
    <scope>provided</scope>
</dependency>

然后编写处理类即可


package com.nChat;
  
import java.io.IOException;  
import java.util.Map;  
import java.util.concurrent.ConcurrentHashMap;  
import javax.websocket.*;  
import javax.websocket.server.PathParam;  
import javax.websocket.server.ServerEndpoint;  
import net.sf.json.JSONObject;  
  
@ServerEndpoint("/websocket/{username}")  
public class WebSocket {  
  
    private static int onlineCount = 0;  
    private static Map<String, WebSocket> clients = new ConcurrentHashMap<String, WebSocket>();  
    private Session session;  
    private String username;  
      
    @OnOpen  
    public void onOpen(@PathParam("username") String username, Session session) throws IOException {  
  
        this.username = username;  
        this.session = session;  
          
        addOnlineCount();  
        clients.put(username, this);  
        System.out.println("已连接");  
    }  
  
    @OnClose  
    public void onClose() throws IOException {  
        clients.remove(username);  
        subOnlineCount();  
    }  
  
    @OnMessage  
    public void onMessage(String message) throws IOException {  
  
        JSONObject jsonTo = JSONObject.fromObject(message);  
          
        if (!jsonTo.get("To").equals("All")){  
            sendMessageTo("给一个人", jsonTo.get("To").toString());  
        }else{  
            sendMessageAll("给所有人");  
        }  
    }  
  
    @OnError  
    public void onError(Session session, Throwable error) {  
        error.printStackTrace();  
    }  
  
    public void sendMessageTo(String message, String To) throws IOException {  
        // session.getBasicRemote().sendText(message);  
        //session.getAsyncRemote().sendText(message);  
        for (WebSocket item : clients.values()) {  
            if (item.username.equals(To) )  
                item.session.getAsyncRemote().sendText(message);  
        }  
    }  
      
    public void sendMessageAll(String message) throws IOException {  
        for (WebSocket item : clients.values()) {  
            item.session.getAsyncRemote().sendText(message);  
        }  
    }  
      
      
  
    public static synchronized int getOnlineCount() {  
        return onlineCount;  
    }  
  
    public static synchronized void addOnlineCount() {  
        WebSocket.onlineCount++;  
    }  
  
    public static synchronized void subOnlineCount() {  
        WebSocket.onlineCount--;  
    }  
  
    public static synchronized Map<String, WebSocket> getClients() {  
        return clients;  
    }  
} 

Jetty实现WebSocket

这个好像不常见…这里就不演示如何配置了,有兴趣可以网上搜相关文章。

总结

WebSocket是HTML5提供的一种在单个TCP连接进行的全双工通讯协议,不用的厂商都可以根据WebSocket API去实现自己的WebSocket框架,比如Spring的WebSocket、Tomcat的WebSocket,我觉得WebSocket和Spring的WebSocket、Tomcat的WebSocket的关系就像JPA和hibernate、Mybatis的关系一样,WebSocket和JPA都是定义了标准,而由各个厂商根据这个标准去实现自己的框架。

完整源代码:https://github.com/xue8/Java-Demo/tree/master/nChat

原文地址:https://ddnd.cn/2019/01/19/spring-websocket/

猜你喜欢

转载自blog.csdn.net/xueba8/article/details/86563692