Java应用层协议WebSocket实现消息推送

目录
  • 前言
  • 浏览器端
  • 服务器端

前言

  大部分的web开发者,开发的业务都是基于Http协议的:前端请求后端接口,携带参数,后端执行业务代码,再返回结果给前端。作者参与开发的项目,有一个报警推送的功能,服务端实时推送报警信息给浏览器端;还有像抖音里面,如果有人关注、回复你的评论时,抖音就会推送相关消息给你了,你就会收到一条消息。

  有些同学会说了,基于Http协议也能实现啊:前端定时访问后端(每隔3s或者几秒),后端返回消息数据,前端拿到后弹出消息。这种方式太low了,而且每个浏览器都这样,使用系统的人一多,服务器的压力就太大了些。那到底用什么技术手段实现呢?我们的主角就登场了。

  WebSocket是在单个TCP连接上进行全双工通信的应用层协议(Http协议也是应用层),浏览器端和服务端都可主动发送数据给另一端。这样是不是比Http协议更适合消息推送这种场景。

浏览器端

  作者建了一个SpringBoot项目,Html放在src\main\resources\static下:

<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<!--    解决中文乱码-->
    <meta charset="UTF-8"/>
    <title></title>
    <script type="text/javascript" src="./js/jquery.min.js"></script>
</head>
<body>
    <input id="input1" type="text" /><br/>
    <input type="button" value="浏览器发送服务端" onclick="btnClick()" />
    <input type="button" value="服务端发送浏览器" onclick="btnClick1()" />
    <input type="button" value="重新打开连接" onclick="btnClick2()" />
    <br/>
    <textarea id="textArea" style="height: 50px"></textarea>
<script>
    var ws;
    webSocketInit();
    function webSocketInit() {
        ws =new WebSocket('ws://localhost:8080/bootdemo/webSocket/10086');
        // 获取连接状态
        console.log('ws连接状态[初始]:' + ws.readyState);
        //监听是否连接成功
        ws.onopen = function () {
            console.log('ws连接状态[成功]:' + ws.readyState);
        };
        // 接听服务器发回的信息并处理展示
        ws.onmessage = function (obj) {
            console.log('接收到来自服务器的消息:');
            var txt = $("#textArea").val();
            $("#textArea").val(txt + "\n" + obj.data);
            $("#textArea").scrollTop($("#textArea")[0].scrollHeight);
            //完成通信后关闭WebSocket连接
            // ws.close();
        };
        // 监听连接关闭事件
        ws.onclose = function () {
            // 监听整个过程中websocket的状态
            console.log('ws连接状态[关闭]:' + ws.readyState);
        };
        // 监听并处理error事件
        ws.onerror = function (error) {
            console.log(error);
        };
    }
    function btnClick() {
        console.log("浏览器端发送消息:");
        //连接成功则发送一个数据
        ws.send($("#input1").val());
    }
    function btnClick1() {
        $.ajax({
            url: 'http://localhost:8080/bootdemo/pushWebSocket/publish?' +
            'userId=10086&message=' + $("#input1").val(),
            type: 'GET',
            success: function (data) {
                // console.log(data);
            }
        });
    }
    function btnClick2() {
        webSocketInit();
    }
</script>
</body>
</html>

服务器端

  先引入依赖:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-websocket</artifactId>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>

  bean上添加@ServerEndpoint,作为WebSocket的服务端。

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@Slf4j
@ServerEndpoint("/webSocket/{userId}")
public class WebSocketServer {
    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private static final CopyOnWriteArraySet<WebSocketServer> webSockets =
    new CopyOnWriteArraySet<>();
    // 用来存在线连接数
    private static final Map<String, Session> sessionPool =
    new HashMap<String, Session>();
    /**
     * 连接成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam(value = "userId")
    String userId) {
        try {
            this.session = session;
            webSockets.add(this);
            sessionPool.put(userId, session);
        }
        catch (Exception e) {
        }
    }
    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("websocket消息: 收到客户端消息:" + message);
    }
    public void sendOneMessage(String userId, String message) {
        Session session = sessionPool.get(userId);
        if (session != null && session.isOpen()) {
            try {
                log.info("服务端推送消息:" + message);
                session.getAsyncRemote().sendText(message);
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  进行注册:

@Configuration
public class WebSocketConfigOne {
    /**
     * 这个bean会自动注册使用了@ServerEndpoint注解声明的对象
     * 没有的话会报404
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

  推送消息的控制器:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/pushWebSocket")
public class WebSocketController {
    @Autowired
    private WebSocketServer webSocketServer;
    @GetMapping("/publish")
    @ResponseBody
    public Map publish(String userId, String message) {
        webSocketServer.sendOneMessage(userId, message);
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", 200);
        return map;
    }
}

  还有我的配置文件application.properties:

  # web port

  server.port=8080

  server.servlet.context-path=/bootdemo

  运行启动类后,访问html(localhost:8080/bootdemo/index.html)如下:

  有的同学一思索,点击图中的第2个按钮"服务端发送浏览器",你这好像也是前端先请求,再推送的消息;我们的WebSocketController#publish方法,在真实的场景下,可以在后端的定时任务中、消息中间件的消费者端调用,不用前端先发送请求。

  当然SpringBoot有专门构建WebSocket服务端的方式。

  核心配置类:

import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.socket.WebSocketHandler;
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.HandshakeInterceptor;
import javax.servlet.http.HttpServletRequest;
import java.util.Map;
@Configuration
@EnableWebSocket
@Slf4j
public class WebSocketConfig1 implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry
    registry) {
        registry.addHandler(new MyWebSocketHandler(), "/webSocket/{userId}")//设置连接路径和处理
                .setAllowedOrigins("*")
                .addInterceptors(new MyWebSocketInterceptor());//设置拦截器
    }
    class MyWebSocketInterceptor implements HandshakeInterceptor {
        //前置拦截一般用来注册用户信息,绑定 WebSocketSession
        @Override
        public boolean beforeHandshake(ServerHttpRequest request,
        ServerHttpResponse response, WebSocketHandler wsHandler,
        Map<String, Object> attributes) throws Exception {
            log.info("前置拦截~~");
            if (!(request instanceof ServletServerHttpRequest)) {
                return true;
            }
            HttpServletRequest servletRequest =
            ((ServletServerHttpRequest)request).getServletRequest();
            Map map = (Map)servletRequest.getAttribute(HandlerMapping.
            URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            String userId = (String)map.get("userId");
            attributes.put("userId", userId);
            return true;
        }
        @Override
        public void afterHandshake(ServerHttpRequest request,
        ServerHttpResponse response, WebSocketHandler wsHandler,
        Exception exception) {
            log.info("后置拦截~~");
        }
    }
}

  核心处理器:

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class MyWebSocketHandler implements WebSocketHandler {
    private static final Map<String, WebSocketSession> SESSIONS =
    new ConcurrentHashMap<>();
	/**
	 * 建立新的socket连接后回调的方法
	 */
    @Override
    public void afterConnectionEstablished(WebSocketSession session)
    throws Exception {
        String userId = (String) session.getAttributes().get("userId");
        SESSIONS.put(userId, session);
    }
	/**
	 * 接收到浏览器端的消息后回调的方法
	 */
    @Override
    public void handleMessage(WebSocketSession session,
    WebSocketMessage<?> message) throws Exception {
        String msg = message.getPayload().toString();
        log.info("收到客户端消息:" + msg);
    }
	/**
	 * 连接出错时回调的方法
	 */
    @Override
    public void handleTransportError(WebSocketSession session,
    Throwable exception) throws Exception {
        log.info("连接出错");
        if (session.isOpen()) {
            session.close();
        }
    }
	/**
	 * 连接关闭时回调的方法
	 */
    @Override
    public void afterConnectionClosed(WebSocketSession session,
    CloseStatus closeStatus) throws Exception {
        log.info("连接关闭:status:" + closeStatus);
    }
	/**
	 * 是否处理部分消息,返回false就行
	 */
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
	/**
	 * 推送消息给浏览器端
	 */
    public void sendMessage(String userId, String message) {
        WebSocketSession webSocketSession = SESSIONS.get(userId);
        if (webSocketSession == null || !webSocketSession.isOpen()) {
            return;
        }
        try {
            webSocketSession.sendMessage(new TextMessage(message));
        }
        catch (Exception ex) {
            log.error("推送消息异常:" + ex);
        }
    }
}

  控制器也改造下:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@Controller
@RequestMapping("/pushWebSocket")
public class WebSocketController {
    @Autowired
    private MyWebSocketHandler handler;
    @GetMapping("/publish")
    @ResponseBody
    public Map publish(String userId, String message) {
        handler.sendMessage(userId, message);
        HashMap<String, Object> map = new HashMap<>();
        map.put("code", 200);
        return map;
    }
}

  前端部分不用做修改,和之前一样的代码。

到此这篇关于Java应用层协议WebSocket实现消息推送的文章就介绍到这了,更多相关Java WebSocket内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java WebSocket实现聊天消息推送功能

    本文实例为大家分享了java WebSocket实现聊天消息推送功能的具体代码,供大家参考,具体内容如下 环境: JDK.1.7.0_51 apache-tomcat-7.0.53 java jar包:tomcat-coyote.jar.tomcat-juli.jar.websocket-api.jar ChatAnnotation消息发送类: import java.io.IOException; import java.util.HashMap; import java.util.Map;

  • Java中websocket消息推送的实现代码

    一.服务层 package com.demo.websocket; import java.io.IOException; import java.util.Iterator; import java.util.concurrent.ConcurrentLinkedQueue; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import org.springframew

  • java后端+前端使用WebSocket实现消息推送的详细流程

    目录 前言 创建WebSocket的简单实例操作流程 1.引入Websocket依赖 2.创建配置类WebSocketConfig 3.创建WebSocketServer 4.websocket调用 总结 前言 在项目的开发时,遇到实现服务器主动发送数据到前端页面的功能的需求.实现该功能不外乎使用轮询和websocket技术,但在考虑到实时性和资源损耗后,最后决定使用websocket.现在就记录一下用Java实现Websocket技术吧~ Java实现Websocket通常有两种方式:1.创建

  • Java应用层协议WebSocket实现消息推送

    目录 前言 浏览器端 服务器端 前言   大部分的web开发者,开发的业务都是基于Http协议的:前端请求后端接口,携带参数,后端执行业务代码,再返回结果给前端.作者参与开发的项目,有一个报警推送的功能,服务端实时推送报警信息给浏览器端:还有像抖音里面,如果有人关注.回复你的评论时,抖音就会推送相关消息给你了,你就会收到一条消息.   有些同学会说了,基于Http协议也能实现啊:前端定时访问后端(每隔3s或者几秒),后端返回消息数据,前端拿到后弹出消息.这种方式太low了,而且每个浏览器都这样,

  • SpringMVC整合websocket实现消息推送及触发功能

    本文为大家分享了SpringMVC整合websocket实现消息推送,供大家参考,具体内容如下 1.创建websocket握手协议的后台 (1)HandShake的实现类 /** *Project Name: price *File Name: HandShake.java *Package Name: com.yun.websocket *Date: 2016年9月3日 下午4:44:27 *Copyright (c) 2016,578888218@qq.com All Rights Rese

  • Springboot+Netty+Websocket实现消息推送实例

    前言 WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据.在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输. Netty框架的优势 1. API使用简单,开发门槛低:  2. 功能强大,预置了多种编解码功能,支持多种主流协议:  3. 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展:  4. 性能高,通过与其他业界主流的NIO框架对比,Netty的综

  • SpringBoot+WebSocket实现消息推送功能

    目录 背景 WebSocket简介 协议原理 WebSocket与HTTP协议的区别 WebSocket特点 应用场景 系统集成Websocket jar包引入 Websocket配置 具体实现 测试示例 页面请求websocket 测试效果 背景 项目中经常会用到消息推送功能,关于推送技术的实现,我们通常会联想到轮询.comet长连接技术,虽然这些技术能够实现,但是需要反复连接,对于服务资源消耗过大,随着技术的发展,HtML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够

  • 详解在Spring Boot框架下使用WebSocket实现消息推送

    spring Boot的学习持续进行中.前面两篇博客我们介绍了如何使用Spring Boot容器搭建Web项目以及怎样为我们的Project添加HTTPS的支持,在这两篇文章的基础上,我们今天来看看如何在Spring Boot中使用WebSocket. 什么是WebSocket WebSocket为浏览器和服务器之间提供了双工异步通信功能,也就是说我们可以利用浏览器给服务器发送消息,服务器也可以给浏览器发送消息,目前主流浏览器的主流版本对WebSocket的支持都算是比较好的,但是在实际开发中使

  • php实现websocket实时消息推送

    php实现websocket实时消息推送,供大家参考,具体内容如下 SocketService.php <?php /** * Created by xwx * Date: 2017/10/18 * Time: 14:33 */ class SocketService { private $address = '0.0.0.0'; private $port = 8083; private $_sockets; public function __construct($address = '',

  • Laravel使用swoole实现websocket主动消息推送的方法介绍

    需求 需要实现一个可以主动触发消息推送的功能,这个可以实现向模板消息那个,给予所有成员发送自定义消息,而不需要通过客户端发送消息,服务端上message中监听传送的消息进行做相对于的业务逻辑. 主动消息推送实现 平常我们采用 swoole 来写 WebSocket 服务可能最多的用到的是open,message,close这三个监听状态,但是万万没有看下下面的onRequest回调的使用,没错,解决这次主动消息推送的就是需要用onRequest回调. 官方文档:正因为swoole_websock

  • java实现钉钉机器人消息推送的示例代码

    先建个钉钉群,并加好机器人 此时,机器人已经添加完毕,接下来编写我们连接机器人小哥的代码 import com.alibaba.fastjson.JSON; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.util.List; import java.util.Map; /** * @author yanghao * @version DingTalkTest.j

  • SpringBoot+WebSocket+Netty实现消息推送的示例代码

    上一篇文章讲了Netty的理论基础,这一篇讲一下Netty在项目中的应用场景之一:消息推送功能,可以满足给所有用户推送,也可以满足给指定某一个用户推送消息,创建的是SpringBoot项目,后台服务端使用Netty技术,前端页面使用WebSocket技术. 大概实现思路: 前端使用webSocket与服务端创建连接的时候,将用户ID传给服务端 服务端将用户ID与channel关联起来存储,同时将channel放入到channel组中 如果需要给所有用户发送消息,直接执行channel组的writ

随机推荐