Netty进阶(二) Springboot集成Netty实现WebSocket服务

前置技术:springboot、netty、websocket的基本概念

WebSocket介绍

在WebSocket概念出来之前,如果页面要不停地显示最新的价格,那么必须不停地刷新页面,或者用一段js代码每隔几秒钟发消息询问服务器数据。 
而使用WebSocket技术之后,当服务器有了新的数据,会主动通知浏览器。

每创建一个浏览器会话就创建一个WebServer对象。 

里面有四个方法:

OnOpen 表示有浏览器链接过来的时候被调用
OnClose 表示浏览器发出关闭请求的时候被调用
OnMessage 表示浏览器发消息的时候被调用
OnError 表示有错误发生,比如网络断开了等等

前端项目代码:整了一个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>Insert title here</title>
</head>
<body>
<div style="width:400px;margin:20px auto;border:1px solid lightgray;padding:20px;text-align:center;">
    当前价格为:¥<span style="color:#FF7519" id="price">10000</span>
</div>
</body>
<script type="text/javascript">
    var websocket=null;
    //判断浏览器是否支持websocket
    if('WebSocket' in window){
        websocket=new WebSocket("ws://localhost:8132/ws");
        websocket.onopen=function(){
            websocket.send("客户端连接成功");
        }
        websocket.onerror=function(){
            websocket.send("客户端连接失败");
        }
        websocket.onclose=function(){
            websocket.send("客户端连接关闭");
        }
        websocket.onmessage=function(e){
            console.log("onmessage-----------------",e)
            send(e.data);
        }
        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
            closeWebSocket();
        }
    }
    else {
        alert('当前浏览器 Not support websocket')
    }
    //将消息显示在网页上
    function send(e) {
        document.getElementById('price').innerHTML =e;
    }
    //关闭WebSocket连接
    function closeWebSocket() {
        websocket.close();
    }
</script>
</html>

Netty服务应用

只贴一下Netty的依赖

     <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.35.Final</version>
        </dependency>

项目结构:

springboot启动类是Main这个类,就不做介绍了。

NettyServer

先来看一下Netty核心逻辑,需要添加四个处理器,三个为自带的,一个为自定义处理器,需要自己去实现。

@Slf4j
public class NettyServer {

    private NioEventLoopGroup bossGroup = new NioEventLoopGroup(3);
    private NioEventLoopGroup workGroup = new NioEventLoopGroup();
    private Channel channel;
    private MyNettyServerHandler nettyServerHandler;

    public NettyServer (MyNettyServerHandler nettyServerHandler){
        this.nettyServerHandler=nettyServerHandler;
    }

    public ChannelFuture start(int port){
        try{
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup,workGroup)
                    //使用哪种通道实现,NioServerSocketChannel为异步的服务器端 TCP Socket 连接。
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    //设置连接队列长度
                    .option(ChannelOption.SO_BACKLOG,1024)
                    //添加拦截处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //websocket协议本身是基于http协议的,所以使用http解编码器
                            pipeline.addLast(new HttpServerCodec());
                            //post请求方式
                            pipeline.addLast(new HttpObjectAggregator(65536));
                            //websocket协议
                            pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
                            pipeline.addLast(nettyServerHandler);
                        }
                    });
            ChannelFuture channelFuture = bootstrap.bind().sync();
            channel=channelFuture.channel();
            log.info("netty 服务开启成功!");
            return channelFuture;
        }catch (Exception e){
            log.info("netty启动错误!{}",e);
        }
        return null;
    }
   public void destory(){
        if (channel!=null){
            channel.close();
        }
       bossGroup.shutdownGracefully();
       workGroup.shutdownGracefully();
       log.info("netty 服务关闭!");
   }
}

MyNettyServerHandler 自定义处理器

@Slf4j
@ChannelHandler.Sharable
public class MyNettyServerHandler extends SimpleChannelInboundHandler {

    private ChannelGroup channels;

    public MyNettyServerHandler(ChannelGroup channels){
        this.channels=channels;
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("读取客户端消息!");
        if (null==msg){
            log.info("数据为空");
           return;
        }
        //第一次读请求是http连接而不是数据
        if (msg instanceof FullHttpRequest){
            FullHttpRequest fullHttpRequest = (FullHttpRequest) msg;
            log.info("第一次http请求:"+fullHttpRequest);
        }
        //正常的数据
        else if (msg instanceof TextWebSocketFrame){
            TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) msg;
            log.info("客户端消息:"+textWebSocketFrame.text());
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("客户端连接成功!");
        channels.add(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("客户端断开连接!");
        channels.remove(ctx.channel());
    }
}

里面引入了一个ChannelGroup对象,这个对象是其实就是它,用于存放所有channel实例的通道组

public class ChannelPool {

    //用于存所有的channel实例
    public static final ChannelGroup channels=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
}

到这里算是单独的Netty服务,怎么将Netty服务和springboot绑定到一起的呢,这时就需要CommandLineRunner接口了。

在springboot项目启动后会自动启动netty服务,netty服务的端口号为springboot服务的端口号加50

public class NettyServerApplication implements CommandLineRunner {

    @Value("${server.port}")
    private int serverPort;

    private NettyServer nettyServer;

    public NettyServerApplication(NettyServer nettyServer){
        this.nettyServer=nettyServer;
    }

    @Override
    public void run(String... args) throws Exception {
        //netty服务为springboot项目端口号加50
        ChannelFuture channelFuture = nettyServer.start(serverPort + 50);
        //添加钩子函数,jvm关闭时一并将注册的服务也关闭
        Runtime.getRuntime().addShutdownHook(new Thread(nettyServer::destory));
        //对关闭通道事件进行监听
        channelFuture.channel().closeFuture().sync();
    }
}

最后通过配置类将这些对象注入进来。

@Configuration
public class WebSocketConfig {

    @Bean
    public NettyServer nettyServer(){
        return new NettyServer(myNettyServerHandler());
    }
    @Bean
    public MyNettyServerHandler myNettyServerHandler(){
        return new MyNettyServerHandler(ChannelPool.channels);
    }
    @Bean
    public NettyServerApplication nettyServerApplication(){
        return new NettyServerApplication(nettyServer());
    }
}

加一个测试入口

@RestController
@RequestMapping("/send")
public class sendController {



    @GetMapping("/ws")
    public void ws() throws Exception{
        while (true){
            TextWebSocketFrame textWebSocketFrame = new TextWebSocketFrame(String.valueOf(new Random().nextInt(10000)));
            ChannelPool.channels.writeAndFlush(textWebSocketFrame);
            Thread.sleep(5000);
        }

    }
}

启动服务器端应用(后端项目)和客户端应用(前端项目)。

访问客户端地址,如我的为http://localhost:8081/websocket.jsp

接着访问服务端接口,我的为http://localhost:8082/send/ws

会发现客户端的金额每五秒钟刷新一次,并且发现客户端并没有主动发起请求。

我们再来打开一个新的页面去访问客户端地址,发现这两个客户端页面会同时刷新,到这里整个项目就结束了。

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值