SpringBoot整合Netty處理WebSocket(支持url參數(shù))

添加MAVEN依賴

<!-- Netty -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.45.Final</version>
</dependency>

WebSocketProperties配置類

為了能在SpringBoot項目中靈活配置相關的值访忿,這里使用了配置類橱乱,并使用了默認值柠掂。

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "chat.websocket")
public class WebSocketProperties {

  private String host = "localhost"; // 監(jiān)聽地址
  private Integer port = 8081; // 監(jiān)聽端口
  private String path = "/ws"; // 請求路徑

}

WebSocket連接通道池

用來管理已經(jīng)連接的客戶端通道扮念,方便數(shù)據(jù)傳輸交互。

@Slf4j
@Component
public class NioWebSocketChannelPool {

  private final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

  /**
   * 新增一個客戶端通道
   *
   * @param channel
   */
  public void addChannel(Channel channel) {
    channels.add(channel);
  }

  /**
   * 移除一個客戶端連接通道
   *
   * @param channel
   */
  public void removeChannel(Channel channel) {
    channels.remove(channel);
  }

}

WebSocket連接數(shù)據(jù)接收處理類

@Slf4j
@Sharable
@Component
public class NioWebSocketHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

  @Autowired
  private NioWebSocketChannelPool webSocketChannelPool;
  @Autowired
  private WebSocketProperties webSocketProperties;

  @Override
  public void channelActive(ChannelHandlerContext ctx) throws Exception {
    log.debug("客戶端連接:{}", ctx.channel().id());
    webSocketChannelPool.addChannel(ctx.channel());
    super.channelActive(ctx);
  }

  @Override
  public void channelInactive(ChannelHandlerContext ctx) throws Exception {
    log.debug("客戶端斷開連接:{}", ctx.channel().id());
    webSocketChannelPool.removeChannel(ctx.channel());
    super.channelInactive(ctx);
  }

  @Override
  public void channelReadComplete(ChannelHandlerContext ctx) {
    ctx.channel().flush();
  }

  @Override
  protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) {
    if (frame instanceof PingWebSocketFrame) {
      pingWebSocketFrameHandler(ctx, (PingWebSocketFrame) frame);
    } else if (frame instanceof TextWebSocketFrame) {
      textWebSocketFrameHandler(ctx, (TextWebSocketFrame) frame);
    } else if (frame instanceof CloseWebSocketFrame) {
      closeWebSocketFrameHandler(ctx, (CloseWebSocketFrame) frame);
    }
  }

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    log.info("數(shù)據(jù)類型:{}", msg.getClass());
    if (msg instanceof FullHttpRequest) {
      fullHttpRequestHandler(ctx, (FullHttpRequest) msg);
    }
    super.channelRead(ctx, msg);
  }

  /**
   * 處理連接請求麦箍,客戶端WebSocket發(fā)送握手包時會執(zhí)行這一次請求
   *
   * @param ctx
   * @param request
   */
  private void fullHttpRequestHandler(ChannelHandlerContext ctx, FullHttpRequest request) {
    String uri = request.uri();
    log.debug("接收到客戶端的握手包:{}", ctx.channel().id());
    Map<String, String> params = RequestUriUtils.getParams(uri);
    log.debug("客戶端請求參數(shù):{}", params);
    if (uri.startsWith(webSocketProperties.getPath()))
      request.setUri(webSocketProperties.getPath());
    else
      ctx.close();
  }

  /**
   * 客戶端發(fā)送斷開請求處理
   *
   * @param ctx
   * @param frame
   */
  private void closeWebSocketFrameHandler(ChannelHandlerContext ctx, CloseWebSocketFrame frame) {
    log.debug("接收到主動斷開請求:{}", ctx.channel().id());
    ctx.close();
  }

  /**
   * 創(chuàng)建連接之后应闯,客戶端發(fā)送的消息都會在這里處理
   *
   * @param ctx
   * @param frame
   */
  private void textWebSocketFrameHandler(ChannelHandlerContext ctx, TextWebSocketFrame frame) {
    String text = frame.text();
    log.debug("接收到客戶端的消息:{}", text);
    // 將客戶端消息回送給客戶端
    ctx.channel().writeAndFlush(new TextWebSocketFrame("你發(fā)送的內(nèi)容是:" + text));
  }

  /**
   * 處理客戶端心跳包
   *
   * @param ctx
   * @param frame
   */
  private void pingWebSocketFrameHandler(ChannelHandlerContext ctx, PingWebSocketFrame frame) {
    ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
  }
}

WebSocket通道連接初始化

@Component
public class NioWebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {

  @Autowired
  private WebSocketProperties webSocketProperties;
  @Autowired
  private NioWebSocketHandler nioWebSocketHandler;

  @Override
  protected void initChannel(SocketChannel socketChannel) {
    socketChannel.pipeline()
        .addLast(new HttpServerCodec())
        .addLast(new ChunkedWriteHandler())
        .addLast(new HttpObjectAggregator(8192))
        .addLast(nioWebSocketHandler)
        .addLast(new WebSocketServerProtocolHandler(webSocketProperties.getPath(), null, true, 65536));
  }
}

Netty服務端

服務器的初始化和銷毀都交給Spring容器期丰。

@Slf4j
@Component
public class NioWebSocketServer implements InitializingBean, DisposableBean {

  @Autowired
  private WebSocketProperties webSocketProperties;
  @Autowired
  private NioWebSocketChannelInitializer webSocketChannelInitializer;

  private EventLoopGroup bossGroup;
  private EventLoopGroup workGroup;
  private ChannelFuture channelFuture;

  @Override
  public void afterPropertiesSet() throws Exception {
    try {
      bossGroup = new NioEventLoopGroup(webSocketProperties.getBoss());
      workGroup = new NioEventLoopGroup(webSocketProperties.getWork());

      ServerBootstrap serverBootstrap = new ServerBootstrap();
      serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)
          .group(bossGroup, workGroup)
          .channel(NioServerSocketChannel.class)
          .localAddress(webSocketProperties.getPort())
          .childHandler(webSocketChannelInitializer);

      channelFuture = serverBootstrap.bind().sync();
    } finally {
      if (channelFuture != null && channelFuture.isSuccess()) {
        log.info("Netty server startup on port: {} (websocket) with context path '{}'", webSocketProperties.getPort(), webSocketProperties.getPath());
      } else {
        log.error("Netty server startup failed.");
        if (bossGroup != null)
          bossGroup.shutdownGracefully().sync();
        if (workGroup != null)
          workGroup.shutdownGracefully().sync();
      }
    }
  }

  @Override
  public void destroy() throws Exception {
    log.info("Shutting down Netty server...");
    if (bossGroup != null && !bossGroup.isShuttingDown() && !bossGroup.isTerminated())
      bossGroup.shutdownGracefully().sync();
    if (workGroup != null && !workGroup.isShuttingDown() && !workGroup.isTerminated())
      workGroup.shutdownGracefully().sync();
    if (channelFuture != null && channelFuture.isSuccess())
      channelFuture.channel().closeFuture().sync();
    log.info("Netty server shutdown.");
  }
}

到此护侮,代碼編寫完成了敌完。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市羊初,隨后出現(xiàn)的幾起案子蠢挡,更是在濱河造成了極大的恐慌,老刑警劉巖凳忙,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異禽炬,居然都是意外死亡涧卵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進店門腹尖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來柳恐,“玉大人,你說我怎么就攤上這事热幔±稚瑁” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵绎巨,是天一觀的道長近尚。 經(jīng)常有香客問我,道長场勤,這世上最難降的妖魔是什么戈锻? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任歼跟,我火速辦了婚禮,結果婚禮上格遭,老公的妹妹穿的比我還像新娘哈街。我一直安慰自己,他們只是感情好拒迅,可當我...
    茶點故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布骚秦。 她就那樣靜靜地躺著,像睡著了一般璧微。 火紅的嫁衣襯著肌膚如雪作箍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天往毡,我揣著相機與錄音蒙揣,去河邊找鬼。 笑死开瞭,一個胖子當著我的面吹牛懒震,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嗤详,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼个扰,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了葱色?” 一聲冷哼從身側響起递宅,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎苍狰,沒想到半個月后办龄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡淋昭,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年俐填,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翔忽。...
    茶點故事閱讀 39,926評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡英融,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出歇式,到底是詐尸還是另有隱情驶悟,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布材失,位于F島的核電站痕鳍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏龙巨。R本人自食惡果不足惜额获,卻給世界環(huán)境...
    茶點故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一够庙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧抄邀,春花似錦耘眨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至奥喻,卻和暖如春偶宫,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背环鲤。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工纯趋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人冷离。 一個月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓吵冒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親西剥。 傳聞我的和親對象是個殘疾皇子痹栖,可洞房花燭夜當晚...
    茶點故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢敢當閱讀 8,911評論 0 50
  • 簡單系統(tǒng)的介紹一下Netty。 在網(wǎng)絡編程體系設計中瞭空,有幾個名詞概念簡單解釋一下: 1 什么是同步揪阿?2 什么是異步...
    郭藝賓閱讀 2,148評論 0 4
  • spring官方文檔:http://docs.spring.io/spring/docs/current/spri...
    牛馬風情閱讀 1,679評論 0 3
  • 【1】 “這次要帶走的,就是這個人類嗎咆畏?”我好奇地看看顯示屏里的那個人類南捂,又轉(zhuǎn)頭看了看隊長。 在地獄惡魔招募所旧找,我...
    靜影怡落閱讀 490評論 0 2
  • 家里還是人多的時間比較溫暖
    橘子隧道閱讀 115評論 0 0