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


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

目录
相关文章
|
9月前
|
前端开发 JavaScript 网络安全
Web网页端即时通讯源码/IM聊天源码RainbowChat-Web
RainbowChat-Web是一套基于MobileIMSDK-Web的网页端IM系统。不同于市面上某些开源练手或淘宝售卖的demo级代码,RainbowChat-Web的产品级代码演化自真正运营过的商业产品,其所依赖的通信层核心SDK已在数年内经过大量客户及其辐射的最终用户的使用和验证。RainbowChat-Web同时也是移动端IM应用RainbowChat的姊妹产品。
293 0
|
7月前
|
数据安全/隐私保护 容器 Go
开源IM即时通讯系统调研
Lumen IM 是一款企业级开源即时通讯工具,前端采用 Vue3 + Naive UI,后端基于 Go 语言,使用 WebSocket 协议。支持 Docker + Nginx 快速部署,适合私有化环境。功能包括文本、图片、文件消息,内置笔记、群聊及消息历史记录。界面美观、功能完善,适用于企业沟通、团队协作及开发者学习。提供前后端源码,便于快速搭建 IM 系统。
开源IM即时通讯系统调研
|
移动开发 网络协议 小程序
基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v10.0版已发布
RainbowChat是一套基于开源IM即时通讯聊天框架 MobileIMSDK 的产品级移动端IM系统。RainbowChat源于真实运营的产品,解决了大量的屏幕适配、细节优化、机器兼容问题。RainbowChat可能是市面上提供im即时通讯聊天源码的,唯一一款同时支持TCP、UDP两种通信协议的IM产品。与姊妹产品RainbowTalk和RainbowChat-Web 技术同源,历经考验。
505 0
基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v10.0版已发布
|
7月前
|
移动开发 网络协议 小程序
鸿蒙NEXT即时通讯/IM系统RinbowTalk v2.4版发布,基于MobileIMSDK框架、ArkTS编写
RainbowTalk是一套基于开源即时通讯讯IM框架 MobileIMSDK 的产品级鸿蒙NEXT端IM系统。纯ArkTS编写、全新开发,没有套壳、也没走捷径,每一行代码都够“纯血”。与姊妹产品RainbowChat和RainbowChat-Web 技术同源,历经考验。
323 1
|
8月前
|
缓存 移动开发 网络协议
纯血鸿蒙NEXT即时通讯/IM系统:RinbowTalk正式发布,全源码、纯ArkTS编写
RainbowTalk是一套基于MobileIMSDK的产品级鸿蒙NEXT端IM系统,目前已正式发布。纯ArkTS、从零编写,无套壳、没走捷径,每一行代码都够“纯”(详见:《RainbowTalk详细介绍》)。 MobileIMSDK是一整套开源IM即时通讯框架,历经10年,超轻量级、高度提炼,一套API优雅支持 UDP 、TCP 、WebSocket 三种协议,支持 iOS、Android、H5、标准Java、小程序、Uniapp、鸿蒙NEXT,服务端基于Netty编写。
631 1
|
8月前
|
测试技术 开发工具 git
基于WebSocket即时通讯im源码| uniapp即时通讯源码| 私有化部署SDK视频安装教程
本项目是基于 ThinkPHP7 和 Swoole 构建的即时通讯 IM 源码,打造了一个简洁美观、移动优先的渐进式 Web 应用。支持从源码构建,并提供详细的安装、配置与使用说明。仓库地址:im.jstxym.top。
|
6月前
|
网络协议 NoSQL API
转转客服IM系统的WebSocket集群架构设计和部署方案
客服IM系统是转转自研的在线客服系统,是用户和转转客服沟通的重要工具,主要包括机器人客服、人工客服、会话分配、技能组管理等功能。在这套系统中,我们使用了很多开源框架和中间件,今天讲一下客服IM系统中WebSocket集群的的实践和应用。
557 141
|
8月前
|
前端开发 JavaScript Java
智能客服系统的技术栈解析-唯一客服系统技术架构优势
“唯一客服系统”采用 Vue.js 2.x + ElementUI 构建前端,实现响应式界面,支持多端适配;后端基于 Golang + Gin + GORM,具备高性能与高并发处理能力。系统支持私有化部署,提供灵活定制、AI 扩展能力,技术栈简洁易维护,兼顾开发者友好与企业级应用需求。
364 1
|
8月前
|
测试技术 Go
客服系统程序入口文件解析-唯一客服系统源码开发
该代码为 Go 语言编写的客服系统命令行程序入口,结构清晰,使用 cmd 包启动业务逻辑,可能基于 cobra 框架实现,具备良好可扩展性与可维护性,适用于服务启动与管理。
300 69

热门文章

最新文章