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

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

[[380697]]

 前言

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

Netty框架的优势

1.API使用简单,开发门槛低;

2.功能强大,预置了多种编解码功能,支持多种主流协议;

3.定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;

4.性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;

5.成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼

提示:以下是本篇文章正文内容,下面案例可供参考

一、引入netty依赖

  1. <dependency> 
  2.    <groupId>io.netty</groupId> 
  3.    <artifactId>netty-all</artifactId> 
  4.    <version>4.1.48.Final</version> 
  5. </dependency> 

二、使用步骤

1.引入基础配置类

  1. package com.test.netty; 
  2.  
  3. public enum Cmd { 
  4.  START("000""連接成功"), 
  5.  WMESSAGE("001""消息提醒"), 
  6.  ; 
  7.  private String cmd; 
  8.  private String desc
  9.  
  10.  Cmd(String cmd, String desc) { 
  11.   this.cmd = cmd; 
  12.   this.desc = desc
  13.  } 
  14.  
  15.  public String getCmd() { 
  16.   return cmd; 
  17.  } 
  18.  
  19.  public String getDesc() { 
  20.   return desc
  21.  } 

2.netty服务启动监听器

  1. package com.test.netty; 
  2.  
  3. import io.netty.bootstrap.ServerBootstrap; 
  4. import io.netty.channel.ChannelFuture; 
  5. import io.netty.channel.ChannelOption; 
  6. import io.netty.channel.EventLoopGroup; 
  7. import io.netty.channel.nio.NioEventLoopGroup; 
  8. import io.netty.channel.socket.nio.NioServerSocketChannel; 
  9. import lombok.extern.slf4j.Slf4j; 
  10. import org.springframework.beans.factory.annotation.Autowired; 
  11. import org.springframework.beans.factory.annotation.Value; 
  12. import org.springframework.boot.ApplicationRunner; 
  13. import org.springframework.context.annotation.Bean; 
  14. import org.springframework.stereotype.Component; 
  15.  
  16. /** 
  17.  * @author test 
  18.  * <p> 
  19.  * 服務啟動監聽器 
  20.  **/ 
  21. @Slf4j 
  22. @Component 
  23. public class NettyServer { 
  24.  
  25.  @Value("${server.netty.port}"
  26.  private int port; 
  27.  
  28.  @Autowired 
  29.  private ServerChannelInitializer serverChannelInitializer; 
  30.  
  31.  @Bean 
  32.  ApplicationRunner nettyRunner() { 
  33.   return args -> { 
  34.    //new 一個主線程組 
  35.    EventLoopGroup bossGroup = new NioEventLoopGroup(1); 
  36.    //new 一個工作線程組 
  37.    EventLoopGroup workGroup = new NioEventLoopGroup(); 
  38.    ServerBootstrap bootstrap = new ServerBootstrap() 
  39.      .group(bossGroup, workGroup) 
  40.      .channel(NioServerSocketChannel.class) 
  41.      .childHandler(serverChannelInitializer) 
  42.      //設置隊列大小 
  43.      .option(ChannelOption.SO_BACKLOG, 1024) 
  44.      // 兩小時內沒有數據的通信時,TCP會自動發送一個活動探測數據報文 
  45.      .childOption(ChannelOption.SO_KEEPALIVE, true); 
  46.    //綁定端口,開始接收進來的連接 
  47.    try { 
  48.     ChannelFuture future = bootstrap.bind(port).sync(); 
  49.     log.info("服務器啟動開始監聽端口: {}", port); 
  50.     future.channel().closeFuture().sync(); 
  51.    } catch (InterruptedException e) { 
  52.     e.printStackTrace(); 
  53.    } finally { 
  54.     //關閉主線程組 
  55.     bossGroup.shutdownGracefully(); 
  56.     //關閉工作線程組 
  57.     workGroup.shutdownGracefully(); 
  58.    } 
  59.   }; 
  60.  } 

3.netty服务端处理器

  1. package com.test.netty; 
  2.  
  3. import com.test.common.util.JsonUtil; 
  4. import io.netty.channel.Channel; 
  5. import io.netty.channel.ChannelHandler; 
  6. import io.netty.channel.ChannelHandlerContext; 
  7. import io.netty.channel.SimpleChannelInboundHandler; 
  8. import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 
  9. import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 
  10. import lombok.Data; 
  11. import lombok.extern.slf4j.Slf4j; 
  12. import org.springframework.beans.factory.annotation.Autowired; 
  13. import org.springframework.stereotype.Component; 
  14.  
  15. import java.net.URLDecoder; 
  16. import java.util.*; 
  17.  
  18. /** 
  19.  * @author test 
  20.  * <p> 
  21.  * netty服務端處理器 
  22.  **/ 
  23. @Slf4j 
  24. @Component 
  25. @ChannelHandler.Sharable 
  26. public class NettyServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { 
  27.  
  28.  @Autowired 
  29.  private ServerChannelCache cache; 
  30.  private static final String dataKey = "test="
  31.  
  32.  @Data 
  33.  public static class ChannelCache { 
  34.  } 
  35.  
  36.  
  37.  /** 
  38.   * 客戶端連接會觸發 
  39.   */ 
  40.  @Override 
  41.  public void channelActive(ChannelHandlerContext ctx) throws Exception { 
  42.   Channel channel = ctx.channel(); 
  43.   log.info("通道連接已打開,ID->{}......", channel.id().asLongText()); 
  44.  } 
  45.  
  46.  @Override 
  47.  public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 
  48.   if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) { 
  49.    Channel channel = ctx.channel(); 
  50.    WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete = (WebSocketServerProtocolHandler.HandshakeComplete) evt; 
  51.    String requestUri = handshakeComplete.requestUri(); 
  52.    requestUri = URLDecoder.decode(requestUri, "UTF-8"); 
  53.    log.info("HANDSHAKE_COMPLETE,ID->{},URI->{}", channel.id().asLongText(), requestUri); 
  54.    String socketKey = requestUri.substring(requestUri.lastIndexOf(dataKey) + dataKey.length()); 
  55.    if (socketKey.length() > 0) { 
  56.     cache.add(socketKey, channel); 
  57.     this.send(channel, Cmd.DOWN_START, null); 
  58.    } else { 
  59.     channel.disconnect(); 
  60.     ctx.close(); 
  61.    } 
  62.   } 
  63.   super.userEventTriggered(ctx, evt); 
  64.  } 
  65.  
  66.  @Override 
  67.  public void channelInactive(ChannelHandlerContext ctx) throws Exception { 
  68.   Channel channel = ctx.channel(); 
  69.   log.info("通道連接已斷開,ID->{},用戶ID->{}......", channel.id().asLongText(), cache.getCacheId(channel)); 
  70.   cache.remove(channel); 
  71.  } 
  72.  
  73.  /** 
  74.   * 發生異常觸發 
  75.   */ 
  76.  @Override 
  77.  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
  78.   Channel channel = ctx.channel(); 
  79.   log.error("連接出現異常,ID->{},用戶ID->{},異常->{}......", channel.id().asLongText(), cache.getCacheId(channel), cause.getMessage(), cause); 
  80.   cache.remove(channel); 
  81.   ctx.close(); 
  82.  } 
  83.  
  84.  /** 
  85.   * 客戶端發消息會觸發 
  86.   */ 
  87.  @Override 
  88.  protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { 
  89.   try { 
  90.    // log.info("接收到客戶端發送的消息:{}", msg.text()); 
  91.    ctx.channel().writeAndFlush(new TextWebSocketFrame(JsonUtil.toString(Collections.singletonMap("cmd""100")))); 
  92.   } catch (Exception e) { 
  93.    log.error("消息處理異常:{}", e.getMessage(), e); 
  94.   } 
  95.  } 
  96.  
  97.  public void send(Cmd cmd, String id, Object obj) { 
  98.   HashMap<String, Channel> channels = cache.get(id); 
  99.   if (channels == null) { 
  100.    return
  101.   } 
  102.   Map<String, Object> data = new LinkedHashMap<>(); 
  103.   data.put("cmd", cmd.getCmd()); 
  104.   data.put("data", obj); 
  105.   String msg = JsonUtil.toString(data); 
  106.   log.info("服務器下發消息: {}", msg); 
  107.   channels.values().forEach(channel -> { 
  108.    channel.writeAndFlush(new TextWebSocketFrame(msg)); 
  109.   }); 
  110.  } 
  111.  
  112.  public void send(Channel channel, Cmd cmd, Object obj) { 
  113.   Map<String, Object> data = new LinkedHashMap<>(); 
  114.   data.put("cmd", cmd.getCmd()); 
  115.   data.put("data", obj); 
  116.   String msg = JsonUtil.toString(data); 
  117.   log.info("服務器下發消息: {}", msg); 
  118.   channel.writeAndFlush(new TextWebSocketFrame(msg)); 
  119.  } 
  120.  

4.netty服务端缓存类

  1. package com.test.netty; 
  2.  
  3. import io.netty.channel.Channel; 
  4. import io.netty.util.AttributeKey; 
  5. import org.springframework.stereotype.Component; 
  6.  
  7. import java.util.HashMap; 
  8. import java.util.concurrent.ConcurrentHashMap; 
  9.  
  10. @Component 
  11. public class ServerChannelCache { 
  12.  private static final ConcurrentHashMap<String, HashMap<String, Channel>> CACHE_MAP = new ConcurrentHashMap<>(); 
  13.  private static final AttributeKey<String> CHANNEL_ATTR_KEY = AttributeKey.valueOf("test"); 
  14.  
  15.  public String getCacheId(Channel channel) { 
  16.   return channel.attr(CHANNEL_ATTR_KEY).get(); 
  17.  } 
  18.  
  19.  public void add(String cacheId, Channel channel) { 
  20.   channel.attr(CHANNEL_ATTR_KEY).set(cacheId); 
  21.   HashMap<String, Channel> hashMap = CACHE_MAP.get(cacheId); 
  22.   if (hashMap == null) { 
  23.    hashMap = new HashMap<>(); 
  24.   } 
  25.   hashMap.put(channel.id().asShortText(), channel); 
  26.   CACHE_MAP.put(cacheId, hashMap); 
  27.  } 
  28.  
  29.  public HashMap<String, Channel> get(String cacheId) { 
  30.   if (cacheId == null) { 
  31.    return null
  32.   } 
  33.   return CACHE_MAP.get(cacheId); 
  34.  } 
  35.  
  36.  public void remove(Channel channel) { 
  37.   String cacheId = getCacheId(channel); 
  38.   if (cacheId == null) { 
  39.    return
  40.   } 
  41.   HashMap<String, Channel> hashMap = CACHE_MAP.get(cacheId); 
  42.   if (hashMap == null) { 
  43.    hashMap = new HashMap<>(); 
  44.   } 
  45.   hashMap.remove(channel.id().asShortText()); 
  46.   CACHE_MAP.put(cacheId, hashMap); 
  47.  } 

5.netty服务初始化器

  1. package com.test.netty; 
  2.  
  3. import io.netty.channel.ChannelInitializer; 
  4. import io.netty.channel.ChannelPipeline; 
  5. import io.netty.channel.socket.SocketChannel; 
  6. import io.netty.handler.codec.http.HttpObjectAggregator; 
  7. import io.netty.handler.codec.http.HttpServerCodec; 
  8. import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 
  9. import io.netty.handler.stream.ChunkedWriteHandler; 
  10. import org.springframework.beans.factory.annotation.Autowired; 
  11. import org.springframework.stereotype.Component; 
  12.  
  13. /** 
  14.  * @author test 
  15.  * <p> 
  16.  * netty服務初始化器 
  17.  **/ 
  18. @Component 
  19. public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> { 
  20.  
  21.  @Autowired 
  22.  private NettyServerHandler nettyServerHandler; 
  23.  
  24.  @Override 
  25.  protected void initChannel(SocketChannel socketChannel) throws Exception { 
  26.   ChannelPipeline pipeline = socketChannel.pipeline(); 
  27.   pipeline.addLast(new HttpServerCodec()); 
  28.   pipeline.addLast(new ChunkedWriteHandler()); 
  29.   pipeline.addLast(new HttpObjectAggregator(8192)); 
  30.   pipeline.addLast(new WebSocketServerProtocolHandler("/test.io"true, 5000)); 
  31.   pipeline.addLast(nettyServerHandler); 
  32.  } 

6.html测试

  1. <!DOCTYPE HTML> 
  2. <html> 
  3.  <head> 
  4.  <meta charset="utf-8"
  5.  <title>test</title> 
  6.   
  7.   <script type="text/javascript"
  8.    function WebSocketTest() 
  9.    { 
  10.    if ("WebSocket" in window) 
  11.    { 
  12.     alert("您的瀏覽器支持 WebSocket!"); 
  13.      
  14.     // 打開一個 web socket 
  15.     var ws = new WebSocket("ws://localhost:port/test.io"); 
  16.      
  17.     ws.onopen = function() 
  18.     { 
  19.      // Web Socket 已連接上,使用 send() 方法發送數據 
  20.      ws.send("發送數據"); 
  21.      alert("數據發送中..."); 
  22.     }; 
  23.      
  24.     ws.onmessage = function (evt)  
  25.     {  
  26.      var received_msg = evt.data; 
  27.      alert("數據已接收..."); 
  28.     }; 
  29.      
  30.     ws.onclose = function() 
  31.     {  
  32.      // 關閉 websocket 
  33.      alert("連接已關閉...");  
  34.     }; 
  35.    } 
  36.     
  37.    else 
  38.    { 
  39.     // 瀏覽器不支持 WebSocket 
  40.     alert("您的瀏覽器不支持 WebSocket!"); 
  41.    } 
  42.    } 
  43.   </script> 
  44.    
  45.  </head> 
  46.  <body> 
  47.   
  48.   <div id="sse"
  49.    <a href="javascript:WebSocketTest()" rel="external nofollow" >運行 WebSocket</a> 
  50.   </div> 
  51.    
  52.  </body> 
  53. </html> 

 7.vue测试

  1. mounted() { 
  2.    this.initWebsocket(); 
  3.   }, 
  4.   methods: { 
  5.    initWebsocket() { 
  6.     let websocket = new WebSocket('ws://localhost:port/test.io?test=123456'); 
  7.     websocket.onmessage = (event) => { 
  8.      let msg = JSON.parse(event.data); 
  9.      switch (msg.cmd) { 
  10.       case "000"
  11.        this.$message({ 
  12.         type: 'success'
  13.         message: "建立實時連接成功!"
  14.         duration: 1000 
  15.        }) 
  16.        setInterval(()=>{websocket.send("heartbeat")},60*1000); 
  17.        break; 
  18.       case "001"
  19.        this.$message.warning("收到一條新的信息,請及時查看!"
  20.        break; 
  21.      } 
  22.     } 
  23.     websocket.onclose = () => { 
  24.      setTimeout(()=>{ 
  25.       this.initWebsocket(); 
  26.      },30*1000); 
  27.     } 
  28.     websocket.onerror = () => { 
  29.      setTimeout(()=>{ 
  30.       this.initWebsocket(); 
  31.      },30*1000); 
  32.     } 
  33.    }, 
  34.   }, 
  35. ![在這裡插入圖片描述](https://img-blog.csdnimg.cn/20210107160420568.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3d1X3Fpbmdfc29uZw==,size_16,color_FFFFFF,t_70#pic_center) 

8.服务器下发消息

  1. @Autowired 
  2.  private NettyServerHandler nettyServerHandler; 
  3. nettyServerHandler.send(CmdWeb.WMESSAGE, id, message); 

到此这篇关于Springboot+Netty+Websocket实现消息推送实例的文章就介绍到这了希望大家以后多多支持麦叔!

 

责任编辑:姜华 来源: 今日Java
相关推荐

2021-03-26 08:16:32

SpringbootWebsocket前端

2021-03-25 08:29:33

SpringBootWebSocket即时消息

2023-08-14 08:01:12

websocket8g用户

2013-05-17 15:34:45

2022-06-28 08:37:07

分布式服务器WebSocket

2017-09-05 15:30:00

JavascriptSocket.ioNode.js

2023-07-26 07:28:55

WebSocket服务器方案

2023-08-09 08:01:00

WebSockett服务器web

2023-09-19 15:33:50

Web实时消息推送

2024-04-07 09:41:18

SignalR实时通信开发

2023-01-13 00:02:41

2023-01-05 09:17:58

2022-12-25 10:47:52

2017-11-23 09:23:05

消息推送系统存储

2018-04-20 09:36:23

NettyWebSocket京东

2017-07-11 13:58:10

WebSocket

2022-07-30 10:08:06

MQTT​协议物联网

2023-09-04 08:00:53

提交事务消息

2024-03-19 08:45:45

WebSocketSpring应用开发

2018-07-19 14:53:23

秒杀websocket异步
点赞
收藏

51CTO技术栈公众号