netty+websocket+quartz实现消息定时推送

netty+websocket+quartz实现消息定时推送&&IM聊天室

netty+websocket+quartz实现消息定时推送_第1张图片

在讲功能实现之前,我们先来捋一下底层的原理,后面附上工程结构及代码

1.NIO

NIO主要包含三大核心部分:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。

下图是NIO通信的结构图。

NIO之所以是NIO,背后有Selector
Selector可以实现: 一个 I/O 线程可以并发处理 N 个客户端连接和读写操作,这从根本上解决了传统同步阻塞 I/O 一连接一线程模型,架构的性能、弹性伸缩能力和可靠性都得到了极大的提升。
netty+websocket+quartz实现消息定时推送_第2张图片

1.1缓冲区(Buffer)

一个用于特定基本数据类 型的容器。由 java.nio 包定义的,所有缓冲区 都是 Buffer 抽象类的子类.。Java NIO 中的 Buffer 主要用于与 NIO 通道进行交互,数据是从通道读入缓冲区,从缓冲区写入通道中。

缓冲区的基本属性

这部分内容较为枯燥,运用netty框架后直接调API,可以暂时跳过
Buffer 中的重要概念

  • 容量 (capacity) :作为一个内存块,Buffer具有一定的固定大小,也称为"容量",缓冲区容量不能为负,并且创建后不能更改。
  • 限制 (limit):表示缓冲区中可以操作数据的大小(limit 后数据不能进行读写)。缓冲区的限制不能为负,并且不能大于其容量。 写入模式,限制等于buffer的容量。读取模式下,limit等于写入的数据量
  • 位置 (position):下一个要读取或写入的数据的索引。缓冲区的位置不能为 负,并且不能大于其限制
  • 标记 (mark)与重置 (reset):标记是一个索引,通过 Buffer 中的 mark() 方法 指定 Buffer 中一个特定的 position,之后可以通过调用 reset() 方法恢复到这 个 position.
    标记、位置、限制、容量遵守以下不变式: 0 <= mark <= position <= limit <= capacity

Buffer常见方法

Buffer clear() 清空缓冲区并返回对缓冲区的引用
Buffer flip() 为 将缓冲区的界限设置为当前位置,并将当前位置充值为 0
int capacity() 返回 Buffer 的 capacity 大小
boolean hasRemaining() 判断缓冲区中是否还有元素
int limit() 返回 Buffer 的界限(limit) 的位置
Buffer limit(int n) 将设置缓冲区界限为 n, 并返回一个具有新 limit 的缓冲区对象
Buffer mark() 对缓冲区设置标记
int position() 返回缓冲区的当前位置 position
Buffer position(int n) 将设置缓冲区的当前位置为 n , 并返回修改后的 Buffer 对象
int remaining() 返回 position 和 limit 之间的元素个数
Buffer reset() 将位置 position 转到以前设置的 mark 所在的位置
Buffer rewind() 将位置设为为 0, 取消设置的 mark

使用Buffer读写数据一般遵循以下四个步骤:

  • 1.写入数据到Buffer
  • 2.调用flip()方法,转换为读取模式
  • 3.从Buffer中读取数据
  • 4.调用buffer.clear()方法或者buffer.compact()方法清除缓冲区

1.2通道(Channel)

通道Channe概述

通道(Channel):由 java.nio.channels 包定义 的。Channel 表示 IO 源与目标打开的连接。 Channel 类似于传统的“流”。只不过 Channel 本身不能直接访问数据,Channel 只能与 Buffer 进行交互。

1、 NIO 的通道类似于流,但有些区别如下:

  • 通道可以同时进行读写,而流只能读或者只能写

  • 通道可以实现异步读写数据

  • 通道可以从缓冲读数据,也可以写数据到缓冲:

2、BIO 中的 stream 是单向的,例如 FileInputStream 对象只能进行读取数据的操作,而 NIO 中的通道(Channel)
是双向的,可以读操作,也可以写操作。

3、Channel 在 NIO 中是一个接口

FileChannel的常用方法

int read(ByteBuffer dst) 从  Channel 到 中读取数据到  ByteBuffer
long  read(ByteBuffer[] dsts) 将  Channel 到 中的数据“分散”到  ByteBuffer[]
int  write(ByteBuffer src) 将  ByteBuffer 到 中的数据写入到  Channel
long write(ByteBuffer[] srcs) 将  ByteBuffer[] 到 中的数据“聚集”到  Channel
long position() 返回此通道的文件位置
FileChannel position(long p) 设置此通道的文件位置
long size() 返回此通道的文件的当前大小
FileChannel truncate(long s) 将此通道的文件截取为给定大小
void force(boolean metaData) 强制将所有对此通道的文件更新写入到存储设备中

1.3选择器(Selector)

选择器(Selector)概述

选择器(Selector) 是 SelectableChannle 对象的多路复用器,Selector 可以同时监控多个 SelectableChannel 的 IO 状况,也就是说,利用 Selector可使一个单独的线程管理多个 Channel。Selector 是非阻塞 IO 的核心。

NIO精华(必看)

  • Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到 Selector(选择器)
  • Selector 能够检测多个注册的通道上是否有事件发生(注意:多个 Channel 以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管多个通道,也就是管理多个连接和请求。
  • 只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程
  • 避免了多线程之间的上下文切换导致的开销

2.Netty

看完上面的NIO基本概述,那么你已经对NIO的通信框架有一定的了解了,下面介绍一下Netty。

Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。摘自百度百科

参考blog:Netty入门教程——认识Netty

3.websocket

websocket实现服务器中消息极速推送到前端。

很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。摘自百度百科

4.quartz

Quartz介绍及入门案例
上面这个是之前用SSM框架实现的quartz定时任务demo,大家感兴趣可以了解一下,本次在SpringBoot项目中用了轻量级的定时框架SpringTask。

1、开启@EnableScheduling注解
netty+websocket+quartz实现消息定时推送_第3张图片

2、@Scheduled定义定时任务
netty+websocket+quartz实现消息定时推送_第4张图片

5.项目工程

5.1工程框架及基础配置信息

netty+websocket+quartz实现消息定时推送_第5张图片

pom.xml

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-websocketartifactId>
        dependency>
        <dependency>
            <groupId>io.nettygroupId>
            <artifactId>netty-allartifactId>
            <version>4.1.32.Finalversion>
            <scope>compilescope>
        dependency>
        
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>1.2.51version>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>fastjsonartifactId>
            <version>RELEASEversion>
            <scope>compilescope>
        dependency>


    dependencies>

application.yml

server:
  port: 8888
spring:
  application:
    name: message-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
#  redis:
#    host: 127.0.0.1
  main:
    allow-bean-definition-overriding: true
mybatis-plus:
  type-aliases-package: com.heima.message.pojo
  configuration:
    map-underscore-to-camel-case: true
#  mapper-locations: mappers/*.xml
logging:
  level:
    com.heima: debug

启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class CfMessageApplication {
     
    public static void main(String[] args) {
     
        SpringApplication.run(CfMessageApplication.class,args);
    }
}

5.2controller

package com.heima.message.controller;

import com.heima.message.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/message")
public class MessageController {
     


    @Autowired
    private MessageService messageService;

    /**
     * 推送给所有用户
     * @param msg
     */
    @PostMapping("/pushAll")
    public void pushToAll(@RequestParam("msg") String msg){
     
        messageService.pushMsgToAll(msg);
    }
    /**
     * 推送给指定用户
     * @param userId
     * @param msg
     */
    @PostMapping("/pushOne")
    public void pushMsgToOne(@RequestParam("userId") String userId,@RequestParam("msg") String msg){
     
        messageService.pushMsgToOne(userId,msg);
    }

    /**
     * 一对多聊天
     */
    @PostMapping("/chatAll")
    public void chatToAll(@RequestParam("senderId")String senderId,@RequestParam("msg") String msg){
     
        messageService.chatToAll(senderId,msg);
    }

    /**
     * 一对一聊天
     */
    @PostMapping("/chatOne")
    public void chatToOne(@RequestParam("senderId")String senderId,
                          @RequestParam("receiverId") String receiverId,
                          @RequestParam("msg")  String msg
    ){
     
        messageService.chatToOne(senderId,receiverId,msg);
    }

}

5.3service

package com.heima.message.service;

public interface MessageService {
     
    /**
     * 推送给指定用户
     *
     * @param userId
     * @param msg
     */
    void pushMsgToOne(String userId, String msg);

    /**
     * 推送给所有用户
     *
     * @param msg
     */
    void pushMsgToAll(String msg);

    /**
     * 一对多聊天
     * @param senderId
     * @param msg
     */
    void chatToAll(String senderId, String msg);

    /**
     * 一对一聊天
     * @param senderId
     * @param receiverId
     * @param msg
     */
    void chatToOne(String senderId, String receiverId, String msg);


}

5.4serviceImpl

package com.heima.message.service.impl;

import com.heima.message.nettywebsocket.NettyConfig;
import com.heima.message.service.MessageService;
import org.springframework.stereotype.Service;
import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.util.concurrent.ConcurrentHashMap;

@Service
public class MessageServiceImpl implements MessageService {
     

    @Override
    public void pushMsgToOne(String userId, String msg){
     
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();
        Channel channel = userChannelMap.get(userId);
        channel.writeAndFlush(new TextWebSocketFrame(msg));
    }
    @Override
    public void pushMsgToAll(String msg){
     
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));
    }

    @Override
    public void chatToAll(String senderId, String msg) {
     
         msg = senderId+"对所有人说: "+msg;
        NettyConfig.getChannelGroup().writeAndFlush(new TextWebSocketFrame(msg));
    }

    @Override
    public void chatToOne(String senderId, String receiverId, String msg) {
     
        msg = senderId+"对"+receiverId+"说: "+msg;
        ConcurrentHashMap<String, Channel> userChannelMap = NettyConfig.getUserChannelMap();
        Channel channel = userChannelMap.get(receiverId);
        channel.writeAndFlush(new TextWebSocketFrame(msg));
    }


}

5.5nettywebsocket

  • NettyConfig
package com.heima.message.nettywebsocket;
import io.netty.channel.Channel;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.util.concurrent.ConcurrentHashMap;

public class NettyConfig {
     
    /**
     * 定义一个channel组,管理所有的channel
     * GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单例
     */
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 存放用户与Chanel的对应信息,用于给指定用户发送消息
     */
    private static ConcurrentHashMap<String, Channel> userChannelMap = new ConcurrentHashMap<>();

    private NettyConfig() {
     
    }

    /**
     * 获取channel组
     *
     * @return
     */
    public static ChannelGroup getChannelGroup() {
     
        return channelGroup;
    }

    /**
     * 获取用户channel map
     *
     * @return
     */
    public static ConcurrentHashMap<String, Channel> getUserChannelMap() {
     
        return userChannelMap;
    }
}
  • NettyServer
package com.heima.message.nettywebsocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
@Component
public class NettyServer {
     
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    /**
     * webSocket协议名
     */
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";

    /**
     * 端口号
     */
    @Value("${webSocket.netty.port:58080}")
    private int port;

    /**
     * webSocket路径
     */
    @Value("${webSocket.netty.path:/webSocket}")
    private String webSocketPath;

    @Autowired
    private WebSocketHandler webSocketHandler;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
    /**
     * 启动
     *
     * @throws InterruptedException
     */
    private void start() throws InterruptedException {
     
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
        bootstrap.group(bossGroup, workGroup);
        // 设置NIO类型的channel
        bootstrap.channel(NioServerSocketChannel.class);
        // 设置监听端口
        bootstrap.localAddress(new InetSocketAddress(port));
        // 连接到达时会创建一个通道
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
     

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
     
                // 流水线管理通道中的处理程序(Handler),用来处理业务
                // webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ObjectEncoder());
                // 以块的方式来写的处理器
                ch.pipeline().addLast(new ChunkedWriteHandler());
                /*
                说明:
        1、http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
        2、这就是为什么,当浏览器发送大量数据时,就会发送多次http请求
         */
                ch.pipeline().addLast(new HttpObjectAggregator(8192));
        /*
        说明:
        1、对应webSocket,它的数据是以帧(frame)的形式传递
        2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
        3、核心功能是将http协议升级为ws协议,保持长连接
        */
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
                // 自定义的handler,处理业务逻辑
                ch.pipeline().addLast(webSocketHandler);

            }
        });
        // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
        ChannelFuture channelFuture = bootstrap.bind().sync();
        log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    }

    /**
     * 释放资源
     *
     * @throws InterruptedException
     */
    @PreDestroy
    public void destroy() throws InterruptedException {
     
        if (bossGroup != null) {
     
            bossGroup.shutdownGracefully().sync();
        }
        if (workGroup != null) {
     
            workGroup.shutdownGracefully().sync();
        }
    }

    @PostConstruct()
    public void init() {
     
        //需要开启一个新的线程来执行netty server 服务器
        new Thread(() -> {
     
            try {
     
                start();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }).start();
    }
}
  • WebSocketHandler
package com.heima.message.nettywebsocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
@Component
public class NettyServer {
     
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    /**
     * webSocket协议名
     */
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";

    /**
     * 端口号
     */
    @Value("${webSocket.netty.port:58080}")
    private int port;

    /**
     * webSocket路径
     */
    @Value("${webSocket.netty.path:/webSocket}")
    private String webSocketPath;

    @Autowired
    private WebSocketHandler webSocketHandler;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
    /**
     * 启动
     *
     * @throws InterruptedException
     */
    private void start() throws InterruptedException {
     
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
        bootstrap.group(bossGroup, workGroup);
        // 设置NIO类型的channel
        bootstrap.channel(NioServerSocketChannel.class);
        // 设置监听端口
        bootstrap.localAddress(new InetSocketAddress(port));
        // 连接到达时会创建一个通道
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
     

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
     
                // 流水线管理通道中的处理程序(Handler),用来处理业务
                // webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ObjectEncoder());
                // 以块的方式来写的处理器
                ch.pipeline().addLast(new ChunkedWriteHandler());
                /*
                说明:
        1、http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
        2、这就是为什么,当浏览器发送大量数据时,就会发送多次http请求
         */
                ch.pipeline().addLast(new HttpObjectAggregator(8192));
        /*
        说明:
        1、对应webSocket,它的数据是以帧(frame)的形式传递
        2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
        3、核心功能是将http协议升级为ws协议,保持长连接
        */
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
                // 自定义的handler,处理业务逻辑
                ch.pipeline().addLast(webSocketHandler);

            }
        });
        // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
        ChannelFuture channelFuture = bootstrap.bind().sync();
        log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    }

    /**
     * 释放资源
     *
     * @throws InterruptedException
     */
    @PreDestroy
    public void destroy() throws InterruptedException {
     
        if (bossGroup != null) {
     
            bossGroup.shutdownGracefully().sync();
        }
        if (workGroup != null) {
     
            workGroup.shutdownGracefully().sync();
        }
    }

    @PostConstruct()
    public void init() {
     
        //需要开启一个新的线程来执行netty server 服务器
        new Thread(() -> {
     
            try {
     
                start();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }).start();
    }
}

5.6scheduler定时器

package com.heima.message.nettywebsocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;
@Component
public class NettyServer {
     
    private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
    /**
     * webSocket协议名
     */
    private static final String WEBSOCKET_PROTOCOL = "WebSocket";

    /**
     * 端口号
     */
    @Value("${webSocket.netty.port:58080}")
    private int port;

    /**
     * webSocket路径
     */
    @Value("${webSocket.netty.path:/webSocket}")
    private String webSocketPath;

    @Autowired
    private WebSocketHandler webSocketHandler;

    private EventLoopGroup bossGroup;
    private EventLoopGroup workGroup;
    /**
     * 启动
     *
     * @throws InterruptedException
     */
    private void start() throws InterruptedException {
     
        bossGroup = new NioEventLoopGroup();
        workGroup = new NioEventLoopGroup();
        ServerBootstrap bootstrap = new ServerBootstrap();
        // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之前的读写操作
        bootstrap.group(bossGroup, workGroup);
        // 设置NIO类型的channel
        bootstrap.channel(NioServerSocketChannel.class);
        // 设置监听端口
        bootstrap.localAddress(new InetSocketAddress(port));
        // 连接到达时会创建一个通道
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
     

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
     
                // 流水线管理通道中的处理程序(Handler),用来处理业务
                // webSocket协议本身是基于http协议的,所以这边也要使用http编解码器
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ObjectEncoder());
                // 以块的方式来写的处理器
                ch.pipeline().addLast(new ChunkedWriteHandler());
                /*
                说明:
        1、http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
        2、这就是为什么,当浏览器发送大量数据时,就会发送多次http请求
         */
                ch.pipeline().addLast(new HttpObjectAggregator(8192));
        /*
        说明:
        1、对应webSocket,它的数据是以帧(frame)的形式传递
        2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
        3、核心功能是将http协议升级为ws协议,保持长连接
        */
                ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
                // 自定义的handler,处理业务逻辑
                ch.pipeline().addLast(webSocketHandler);

            }
        });
        // 配置完成,开始绑定server,通过调用sync同步方法阻塞直到绑定成功
        ChannelFuture channelFuture = bootstrap.bind().sync();
        log.info("Server started and listen on:{}", channelFuture.channel().localAddress());
        // 对关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    }

    /**
     * 释放资源
     *
     * @throws InterruptedException
     */
    @PreDestroy
    public void destroy() throws InterruptedException {
     
        if (bossGroup != null) {
     
            bossGroup.shutdownGracefully().sync();
        }
        if (workGroup != null) {
     
            workGroup.shutdownGracefully().sync();
        }
    }

    @PostConstruct()
    public void init() {
     
        //需要开启一个新的线程来执行netty server 服务器
        new Thread(() -> {
     
            try {
     
                start();
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
        }).start();
    }
}

5.7前端


<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>cheung的账号title>
    <link rel="stylesheet" href="./index.css">
head>
<body>
<div class="websocket">
    <div class="receive">
        <p>消息列表p>
        <div id="receive-box">div>
    div>

    <div class="send">
        <textarea type="text" id="msg-need-send">textarea>
        <p>
            <select>
                

netty+websocket+quartz实现消息定时推送_第6张图片

PostMan测试

netty+websocket+quartz实现消息定时推送_第7张图片

Memorial Day is 553 days
I miss you
xiaokeai

你可能感兴趣的:(java开发技巧集锦,java,netty,nio,websocket,quartz)