SpringBoot+Netty开发IM即时通讯系列(二)

简介: 通过JS以Ajax异步地让浏览器每隔一段时间(10S)发送请求到后端,去询问服务端是否有新消息、新状态等,如果有则取出并通过前端再渲染。但这很容易造成无限循环,也就是前端Ajax会不停地循环后端的数据

SpringBoot+Netty开发IM即时通讯系列(二)


上篇 “SpringBoot+Netty开发IM即时通讯系列(一)”介绍了Netty与NIO等基础知识点,感兴趣的可以去看下:


https://blog.csdn.net/qq_26975307/article/details/85004424


本篇使用Netty+WebSocket构建一个最简单的Demo在线IM通信项目(关于接口或类的作用等已在注释中注明)。


前言


实时通信的分类:


(1)Ajax轮训


       通过JS以Ajax异步地让浏览器每隔一段时间(10S)发送请求到后端,去询问服务端是否有新消息、新状态等,如果有则取出并通过前端再渲染。但这很容易造成无限循环,也就是前端Ajax会不停地循环后端的数据                                 (使用场景:浏览器不需要一直刷新,简单的后台管理系统中的数据更新等)



(2)Long Pull


       与Ajax轮训类似,也是使用异步请求,只不过它的轮训方式不太友好,阻塞式轮训:当客户端发起请求之后,服务端如果未响应,则Long Pull就不会有响应,直到服务端返回response。过程中不停地建立Http请求,等待服务器端进行处理,被动响应,缺点也是非常明显,也很耗费资源,性能低。



(3)webSokect - 推荐


       Http本身就不支持长连接,Http1.1支持长连接,WebSokect就是使用了Http1.1协议来完成一小部分的握手,简单来讲就是,客户端发起请求到服务端,服务端会去找一个副助理,找到之后服务器端会和客户端一直保持连接,为客户端进行服务,并且可以主动推送一些消息给客户端。


关于WebSocket


WebSokect有哪些协议,又有什么优点?


           1)首先WebSokect相对于Http这种非持久化来讲,是一种持久化的协议,Http的生命周期可以说是通过一个request来进行判定,有一个request请求到后端,后端也会相应的返回一个response给客户端,或者有多个request对应到多个response,两者之间都是一一对应的,有多少个request请求就会有多少个response相应,不会有偏差。此时response其实也是被动的,它不能由服务器端主动发起相应,必须先有request请求。


           2)WebSokect由此诞生,它使得资源不会像以前一样浪费,并且它也是非常的主动,只要链接一旦被建立完毕之后,那么服务端就可以不停的主动推送消息给客户端,客户端不需要主动请求服务端也可以达到一样的效果。 也就是说,只要建立一次Http请求就能达到信息的源源不断的传输。类似于在线Online小游戏,一开始建立连接,就可以一直保持在线了。


WebSocket API(最基础也是最常用的几个)


(1)var socket=new WebSocket("ws://[ip]:[port]");

(2)生命周期:onopen() onmessage() onerror() onclose()

(3)主动方法:Socket.send() Socket.close()


参考API:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket


导入相关依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.pubing</groupId>
  <artifactId>helloNetty</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <dependencies>
    <dependency>
      <groupId>io.netty</groupId>
      <artifactId>netty-all</artifactId>
      <version>4.1.25.Final</version>
    </dependency>
  </dependencies>
</project>

创建服务器启动类 WebSocketServer  


package com.phubing.websokect;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
 * 用于和客户端进行连接
 * 
 * @author phubing
 *
 */
public class WebSocketServer {
  public static void main(String[] args) throws InterruptedException {
    //定义线程组
    EventLoopGroup mainGroup =  new NioEventLoopGroup();
    EventLoopGroup subGroup =  new NioEventLoopGroup();
    try {
    ServerBootstrap server = new ServerBootstrap();
    server.group(mainGroup, subGroup)
    //channel类型
    .channel(NioServerSocketChannel.class)
    //针对subGroup做的子处理器,childHandler针对WebSokect的初始化器
    .childHandler(new WebSocketinitializer());
    //绑定端口并以同步方式进行使用
    ChannelFuture channelFuture = server.bind(10086).sync();
    //针对channelFuture,进行相应的监听
    channelFuture.channel().closeFuture().sync();
    }finally {
      //针对两个group进行优雅地关闭
      mainGroup.shutdownGracefully();
      subGroup.shutdownGracefully();
    }
  }
}


创建WebSocket初始化器WebSocketinitializer


package com.phubing.websokect;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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.stream.ChunkedWriteHandler;
public class WebSocketinitializer extends ChannelInitializer<SocketChannel>{
  @Override
  protected void initChannel(SocketChannel socketChannel) throws Exception {
    //从Channel中获取对应的pipeline
    ChannelPipeline channelPipeline = socketChannel.pipeline();
    //添加相应的助手类与处理器
    /**
     * WebSokect基于Http,所以要有相应的Http编解码器,HttpServerCodec()
     */
    channelPipeline.addLast(new HttpServerCodec());
    //在Http中有一些数据流的传输,那么数据流有大有小,如果说有一些相应的大数据流处理的话,需要在此添加
    //ChunkedWriteHandler:为一些大数据流添加支持
    channelPipeline.addLast(new ChunkedWriteHandler());
    //UdineHttpMessage进行处理,也就是会用到request以及response
    //HttpObjectAggregator:聚合器,聚合了FullHTTPRequest、FullHTTPResponse。。。,当你不想去管一些HttpMessage的时候,直接把这个handler丢到管道中,让Netty自行处理即可
    channelPipeline.addLast(new HttpObjectAggregator(2048*64));
    //================华丽的分割线:以上是用于支持Http协议================
    //================华丽的分割线:以下是用于支持WebSoket==================
    // /ws:一开始建立连接的时候会使用到,可自定义
    //WebSocketServerProtocolHandler:给客户端指定访问的路由(/ws),是服务器端处理的协议,当前的处理器处理一些繁重的复杂的东西,运行在一个WebSocket服务端
    //另外也会管理一些握手的动作:handshaking(close,ping,pong) ping + pong = 心跳,对于WebSocket来讲,是以frames进行传输的,不同的数据类型对应的frames也不同
    channelPipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
    //添加自动handler,读取客户端消息并进行处理,处理完毕之后将相应信息传输给对应客户端
    channelPipeline.addLast(new ChatHandler());
  }
}


添加自定义助手ChatHandler


package com.phubing.websokect;
import java.time.LocalDate;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;
//TextWebSocketFrame:处理消息的handler,在Netty中用于处理文本的对象,frames是消息的载体
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
  //用于记录和管理所有客户端的channel,可以把相应的channel保存到一整个组中
  //DefaultChannelGroup:用于对应ChannelGroup,进行初始化
  private static ChannelGroup channelClient =  new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
  @Override
  protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
    //text()获取从客户端发送过来的字符串
    String content = msg.text();
    System.out.println("客户端传输的数据:"+content);
    //针对channel进行发送,客户端对应的是channel
    /**
     * 方式一
     */
    for (Channel channel : channelClient) {
      //循环对每一个channel对应输出即可(往缓冲区中写,写完之后再刷到客户端)
      //注:writeAndFlush不可以使用String,因为传输的载体是一个TextWebSocketFrame,需要把消息通过载体再刷到客户端
      channel.writeAndFlush(new TextWebSocketFrame("【服务器于 " + LocalDate.now() + "接收到消息:】 ,消息内容为:" +content));
    }   
    /**
     * 方式二
    channelClient.writeAndFlush(new TextWebSocketFrame("【服务器于 " + LocalDate.now() + "接收到消息:】 ,消息内容为:" +content))
     */
  }
  //当客户端连接服务端(或者是打开连接之后)
  @Override
  public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    //获取客户端所对应的channel,添加到一个管理的容器中即可
    channelClient.add(ctx.channel());
  }
  //客户端断开
  @Override
  public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    //实际上是多余的,只要handler被移除,client会自动的把对应的channel移除掉
    channelClient.remove(ctx.channel());
    //每一而channel都会有一个长ID与短ID
    //一开始channel就有了,系统会自动分配一串很长的字符串作为唯一的ID,如果使用asLongText()获取的ID是唯一的,asShortText()会把当前ID进行精简,精简过后可能会有重复
    System.out.println("channel的长ID:"+ctx.channel().id().asLongText());
    System.out.println("channel的短ID:"+ctx.channel().id().asShortText());
  }
}


此时,服务端已完成,接下来再新建一个前端页面,用于发送文本与显示服务端推送的数据


<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Netty+WebSocket案例</title>
  </head>
  <body>
    <div id="">发送消息:</div><br>
    <input type="text" name="messageContent" id="messageContent"/>
    <input type="button" name="" id="" value="发送" onclick="CHAT.chat()"/>
    <hr>
    <div id="">接收消息:</div><br>
    <div id="receiveNsg" style="background-color: gainsboro;"></div>
    <script type="text/javascript">
      window.CHAT = {
        socket: null,
        //初始化
        init: function(){
          //首先判断浏览器是否支持WebSocket
          if (window.WebSocket){
            CHAT.socket = new WebSocket("ws://localhost:10086/ws");
            CHAT.socket.onopen = function(){
              console.log("客户端与服务端建立连接成功");
            },
            CHAT.socket.onmessage = function(e){
              console.log("接收到消息:"+e.data);
              var receiveNsg = window.document.getElementById("receiveNsg");
              var html = receiveNsg.innerHTML;
              receiveNsg.innerHTML = html + "<br>" + e.data; 
            },
            CHAT.socket.onerror = function(){
              console.log("发生错误");
            },
            CHAT.socket.onclose = function(){
              console.log("客户端与服务端关闭连接成功");
            }           
          }else{
            alert("8102年都过了,升级下浏览器吧");
          }
        },
        chat: function(){
          var msg = window.document.getElementById("messageContent");
          CHAT.socket.send(msg.value);
        }
      }
      CHAT.init();
    </script>
  </body>
</html>


端口号、IP地址、WebSocket的服务端提供名称一定要与前端相对应,否则会出错


例如:服务端给客户端指定访问的路由为:/ws,IP地址为:192.168.45.96,端口号为:10086


那么前端在建立WebSocket连接时填写为:


CHAT.socket = new WebSocket("ws://localhost:10086/ws");   //ws://   为固定写法


最后来看看效果图

20181217171839167.png


如有不当之处请指出,虚心接受建议与批评。

目录
相关文章
|
12天前
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
161 1
|
30天前
|
NoSQL Java Redis
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
这篇文章介绍了Redis的基本命令,并展示了如何使用Netty框架直接与Redis服务器进行通信,包括设置Netty客户端、编写处理程序以及初始化Channel的完整示例代码。
37 1
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
|
3月前
|
API 开发者
Netty运行原理问题之Netty实现低开发门槛的问题如何解决
Netty运行原理问题之Netty实现低开发门槛的问题如何解决
|
3月前
|
前端开发 Java Spring
springboot 整合 netty框架, 实现 心跳检测,自动重连
springboot 整合 netty框架, 实现 心跳检测,自动重连
|
3月前
|
前端开发 网络协议
Netty实战巅峰:从零构建高性能IM即时通讯系统,解锁并发通信新境界
【8月更文挑战第3天】Netty是一款高性能、异步事件驱动的网络框架,适用于开发高并发网络应用,如即时通讯(IM)系统。本文将指导你利用Netty从零构建高性能IM程序,介绍Netty基础及服务器/客户端设计。服务器端使用`ServerBootstrap`启动,客户端通过`Bootstrap`连接服务器。示例展示了简单的服务器启动过程。通过深入学习,可进一步实现用户认证等功能,打造出更完善的IM系统。
113 1
|
3月前
|
编解码 NoSQL Redis
(十一)Netty实战篇:基于Netty框架打造一款高性能的IM即时通讯程序
关于Netty网络框架的内容,前面已经讲了两个章节,但总归来说难以真正掌握,毕竟只是对其中一个个组件进行讲解,很难让诸位将其串起来形成一条线,所以本章中则会结合实战案例,对Netty进行更深层次的学习与掌握,实战案例也并不难,一个非常朴素的IM聊天程序。
|
4月前
|
存储 SQL 测试技术
基于SpringBoot+Vue交通管理在线服务系统的开发(源码+部署说明+演示视频+源码介绍+lw)(2)
基于SpringBoot+Vue交通管理在线服务系统的开发(源码+部署说明+演示视频+源码介绍+lw)
115 2
|
4月前
|
JavaScript Java 关系型数据库
基于SpringBoot+Vue交通管理在线服务系统的开发(源码+部署说明+演示视频+源码介绍+lw)(1)
基于SpringBoot+Vue交通管理在线服务系统的开发(源码+部署说明+演示视频+源码介绍+lw)
102 1
|
4月前
|
小程序
【微信小程序-原生开发】客服
【微信小程序-原生开发】客服
112 0
|
27天前
|
存储 自然语言处理 机器人
实战揭秘:当RAG遇上企业客服系统——从案例出发剖析Retrieval-Augmented Generation技术的真实表现与应用局限,带你深入了解背后的技术细节与解决方案
【10月更文挑战第3天】随着自然语言处理技术的进步,结合检索与生成能力的RAG技术被广泛应用于多个领域,通过访问外部知识源提升生成内容的准确性和上下文一致性。本文通过具体案例探讨RAG技术的优势与局限,并提供实用建议。例如,一家初创公司利用LangChain框架搭建基于RAG的聊天机器人,以自动化FAQ系统减轻客服团队工作负担。尽管该系统在处理简单问题时表现出色,但在面对复杂或多步骤问题时存在局限。此外,RAG系统的性能高度依赖于训练数据的质量和范围。因此,企业在采用RAG技术时需综合评估需求和技术局限性,合理规划技术栈,并辅以必要的人工干预和监督机制。
70 3