SpringBoot2+Netty+WebSocket(netty实现websocket)

2023-11-11

##### 一、SpringBoot2+Netty+WebSocket(netty实现websocket,支持URL参数)
原文链接: https://zhengkai.blog.csdn.net/article/details/91552993

思路: 用netty实现 后台的 websocket 相当于前端利用websocket协议 后端用netty实现复杂的业务逻辑

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

SpringBootApplication
启动器中需要new一个NettyServer,并显式调用启动netty。

@SpringBootApplication
public class SpringCloudStudyDemoApplication {
	public static void main(String[] args) {
		SpringApplication.run(SpringCloudStudyDemoApplication.class,args);
		try {
			new NettyServer(12345).start();
			System.out.println("https://blog.csdn.net/moshowgame");
			System.out.println("http://127.0.0.1:6688/netty-websocket/index");
		}catch(Exception e) {
			System.out.println("NettyServerError:"+e.getMessage());
		}
	}
}

其中 ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10)); 请求为 websocket

/**
 * NettyServer Netty服务器配置
 * @author zhengkai.blog.csdn.net
 * @date 2019-06-12
 */
public class NettyServer {
    private final int port;
 
    public NettyServer(int port) {
        this.port = port;
    }
 
    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
 
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap sb = new ServerBootstrap();
            sb.option(ChannelOption.SO_BACKLOG, 1024);
            sb.group(group, bossGroup) // 绑定线程池
                    .channel(NioServerSocketChannel.class) // 指定使用的channel
                    .localAddress(this.port)// 绑定监听端口
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作
 
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("收到新连接");
                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
                            ch.pipeline().addLast(new HttpServerCodec());
                            //以块的方式来写的处理器
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new HttpObjectAggregator(8192));
                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));
                            ch.pipeline().addLast(new MyWebSocketHandler());
                        }
                    });
            ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
            System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
            cf.channel().closeFuture().sync(); // 关闭服务器通道
        } finally {
            group.shutdownGracefully().sync(); // 释放线程池资源
            bossGroup.shutdownGracefully().sync();
        }
    }
}

MyChannelHandlerPool
通道组池,管理所有websocket连接

/**
 * MyChannelHandlerPool
 * 通道组池,管理所有websocket连接
 * @author zhengkai.blog.csdn.net
 * @date 2019-06-12
 */
public class MyChannelHandlerPool {

    public MyChannelHandlerPool(){}

    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

}

MyWebSocketHandler
处理ws一下几种情况:

channelActive与客户端建立连接
channelInactive与客户端断开连接
channelRead0客户端发送消息处理

/**
 * NettyServer Netty服务器配置
 * @author zhengkai.blog.csdn.net
 * @date 2019-06-12
 */
public class NettyServer {
    private final int port;
 
    public NettyServer(int port) {
        this.port = port;
    }
 
    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
 
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap sb = new ServerBootstrap();
            sb.option(ChannelOption.SO_BACKLOG, 1024);
            sb.group(group, bossGroup) // 绑定线程池
                    .channel(NioServerSocketChannel.class) // 指定使用的channel
                    .localAddress(this.port)// 绑定监听端口
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作
 
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("收到新连接");
                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
                            ch.pipeline().addLast(new HttpServerCodec());
                            //以块的方式来写的处理器
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new HttpObjectAggregator(8192));
                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", "WebSocket", true, 65536 * 10));
                            ch.pipeline().addLast(new MyWebSocketHandler());
                        }
                    });
            ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
            System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
            cf.channel().closeFuture().sync(); // 关闭服务器通道
        } finally {
            group.shutdownGracefully().sync(); // 释放线程池资源
            bossGroup.shutdownGracefully().sync();
        }
    }
}

socket.html
主要是连接ws,发送消息,以及消息反馈

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Netty-Websocket</title>
    <script type="text/javascript">
        // by zhengkai.blog.csdn.net
        var socket;
        if(!window.WebSocket){
            window.WebSocket = window.MozWebSocket;
        }
        if(window.WebSocket){
            socket = new WebSocket("ws://127.0.0.1:12345/ws");
            socket.onmessage = function(event){
                var ta = document.getElementById('responseText');
                ta.value += event.data+"\r\n";
            };
            socket.onopen = function(event){
                var ta = document.getElementById('responseText');
                ta.value = "Netty-WebSocket服务器。。。。。。连接  \r\n";
            };
            socket.onclose = function(event){
                var ta = document.getElementById('responseText');
                ta.value = "Netty-WebSocket服务器。。。。。。关闭 \r\n";
            };
        }else{
            alert("您的浏览器不支持WebSocket协议!");
        }
        function send(message){
            if(!window.WebSocket){return;}
            if(socket.readyState == WebSocket.OPEN){
                socket.send(message);
            }else{
                alert("WebSocket 连接没有建立成功!");
            }
 
        }
 
    </script>
</head>
<body>
<form onSubmit="return false;">
    <label>ID</label><input type="text" name="uid" value="${uid!!}" /> <br />
    <label>TEXT</label><input type="text" name="message" value="这里输入消息" /> <br />
    <br /> <input type="button" value="发送ws消息"
                  onClick="send(this.form.uid.value+':'+this.form.message.value)" />
    <hr color="black" />
    <h3>服务端返回的应答消息</h3>
    <textarea id="responseText" style="width: 1024px;height: 300px;"></textarea>
</form>
</body>
</html>

Controller
写好了html当然还需要一个controller来引导页面。

@RestController
public class IndexController {
	
	@GetMapping("/index")
	public ModelAndView  index(){
		ModelAndView mav=new ModelAndView("socket");
		mav.addObject("uid", RandomUtil.randomNumbers(6));
		return mav;
	}
	
}

版权声明:本文为CSDN博主「Moshow郑锴」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/moshowgame/article/details/91552993

涉及问题解答**
1、
3个问题: 1.如何主动后台推送,而不是通过前端发送才进行消息推送 2.如果需要部署到域名,不用ip+port访问,使用了netty,端口肯定与项目本身不同时,该如何统一项目和ws的域名地址 3.使用前后端跨域要怎么处理? 小白一枚。希望博主从百忙中抽空回复2 年前回复
西城无故事回复: 1. 用其他的触发条件 (如定时器、线程)触发发送操作。 23. nginx配置反向代理2

2、
问个问题,虽然netty很好,但毕竟springboot原生也支持websocket,而且开发体验更棒,应用中也没发现问题,使用netty是不是多此一举?10 月前回复
留住风的小树回复華灯初上:可以做到方法级别的权限控制,因为调用是wss,最终还是要进到你写的方法的,可以在websocket请求时带上特定的参数7 月前回复
留住风的小树回复華灯初上:可以做到方法级别的权限控制,因为调用是wss,最终还是要尽到你写的方法的,可以在websocket请求时带上特定的参数7 月前回复
码哥码哥華灯初上回复留住风的小树:我有个问题,如果这样集成netty 那不相当于重新开一个端口,就把springmvc就屏蔽了,这样对权限的认证需要自己重写? 我的理解对吗?7 月前回复
留住风的小树回复:性能问题而已

##### 二、SpringBoot2.0集成WebSocket,实现后台向前端推送信息
原文连接:链接: https://blog.csdn.net/moshowgame/article/details/80275084#google_vignette

  1. 真实业务场景下,一个项目可能有多个页面都要使用websocket,创建一个链接,消息中使用type字段区分业务类型,和后端websocketServer进行消息传输。 是这么做吧? 还是每个使用websocket的页面,单独new 一个websocket实例
  2. 放行后你还会遇到一些问题,比如有些公司是由 nginx+gateway进行代理转发请求的, 所以你会遇到这样的错误 WebSocket连接错误Error during WebSocket handshake Unexpected response code 404 你需要修改nginx配置,参考这篇文章即可解决 https://www.cnblogs.com/cnsyear/p/12635301.html
  3. 关于第一次连接,请求头怎么携带token呢?楼主有没有试过stomp协议的websocket?
  4. WebSocketServer 是一个单例。那么每次onOpen时,this.session 和 this.user 都会发生改变。这样是不是有问题?
  5. 例子我都实现成功了,但是我就是有一点概念没有想明白,困扰我好几天了,就是websocket连接成功之后,后端如何向前端发送消息,如果说后端用定时器,然后定时执行推送给前端的话,和前端用setTimeOut轮询差不多啊,就是这一点我一直没有想明白,希望楼主帮我梳理一下,谢谢!
  6. 关于controller调用controller/service调用service/util调用service/websocket中autowired的解决方法 ****
  7. @Component和@ServerEndpoint关于是否单例模式,能否使用static Map等一些问题的解答
    看到大家都在热心的讨论关于是否单例模式这个问题,请大家相信自己的直接,如果websocket是单例模式,还怎么服务这么多session呢。
    websocket是原型模式,@ServerEndpoint每次建立双向通信的时候都会创建一个实例,区别于spring的单例模式。
    Spring的@Component默认是单例模式,请注意,默认 而已,是可以被改变的。
    这里的@Component仅仅为了支持@Autowired依赖注入使用,如果不加则不能注入任何东西,为了方便。
    什么是prototype 原型模式? 基本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例B的话,就需要使用原型模式。
    关于在原型模式下使用static 的webSocketMap,请注意这是ConcurrentHashMap ,也就是线程安全/线程同步的,而且已经是静态变量作为全局调用,这种情况下是ok的,或者大家如果有顾虑或者更好的想法的化,可以进行改进。 例如使用一个中间类来接收和存放session。
    为什么每次都@OnOpen都要检查webSocketMap.containsKey(userId) ,首先了为了代码强壮性考虑,假设代码以及机制没有问题,那么肯定这个逻辑是废的对吧。但是实际使用的时候发现偶尔会出现重连失败或者其他原因导致之前的session还存在,这里就做了一个清除旧session,迎接新session的功能。
  8. 因为楼上的没看全,有两个注解偏偏只看一个,@ServerEndpoint代表websocket,是原型模式,每次建立双向通信的时候都会创建一个实例,区别于spring的单例模式 。。。这里的@Component只是为了方便@Autowired注入而已,没有实际意义,也不是单例模式的代表
  9. 如果自己在本地做demo,按照上面博客 照做没有什么问题,但是工作当中,我们都是会走gateway网关的 所以我们会碰到一个问题,那就是请求会被拦截,所以你需要对ws请求放行,可以在配置中心放行 如 - /imserver/**
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot2+Netty+WebSocket(netty实现websocket) 的相关文章

随机推荐

  • SSDA-YOLO:新的YOLOv5改进方法——用于跨域目标检测的半监督域自适应YOLO方法

    文章转载来源于自动驾驶之心 作者汽车人 域自适应目标检测 DAOD 旨在缓解跨域差异导致的传输性能下降 然而 大多数现有的DAOD方法由计算密集的两级检测器主导 这不是工业应用的首选 本文提出了一种新的基于半监督域自适应YOLO SSDA
  • Bailian3709 2进制转化为3进制【进制转换】

    题目链接 2进制转化为3进制 总时间限制 1000ms 内存限制 65536kB 描述 输入一个2进制的数 要求输出该2进制数的3进制表示 在3进制的表示中 只有0 1 2三种符号 输入 第1行是测试数据的组数n 后面跟着n行输入 每组测试
  • 彻底弄懂CommonJS和AMD/CMD!

    JS中的模块规范 CommonJS AMD CMD 如果你听过js模块化这个东西 那么你就应该听过或CommonJS或AMD甚至是CMD这些规范咯 我也听过 但之前也真的是听听而已 现在就看看吧 这些规范到底是啥东西 干嘛的 一 Commo
  • Ubuntu 搜狗输入法显示繁体,一直不显示简体

    1 问题 在ubuntu系统下 安装好sogou输入法之后 用了一段时间之后 输入拼音之后 老是出现繁体字 很烦 原因 可能误触了繁体简体开关 2 解决办法 按住shift 不放 同时 按下ctrl 和F键 即可 有些博客和评论说这种方式在
  • linux各种文件系统挂载总结

    mount语法 mount t 类型 o 挂接方式 源路径 目标路径 t 选项 iso9660 光盘或光盘镜像 msdos DOS fat16文件系统 vfat Windows 9x fat32文件系统 ntfs Windows NT nt
  • Redis核心知识点

    Redis面试精选 1 redis 简介 简单来说 redis 就是一个数据库 不过与传统https download csdn net download liyangxueit 12307486库不同的是 redis 的数据是存在内存中的
  • ++k与k++区别 和双目运算符结合

    最近在写代码时发现了这样一个错误 题目如下 链接 Hdu 1027 关键代码如下 int k 1 while next permutation s begin s end if k m 注意 问题就出在这里 for int i 0 i
  • C#编程和网络编程入门

    文章目录 一 用C 编写一个命令行 控制台程序 二 用C 编写一个简单的Form窗口程序 参考 一 用C 编写一个命令行 控制台程序 要求 用C 编写一个命令行 控制台hello world程序 实现如下功能 在屏幕上连续输出50行 hel
  • Qt的MVC模型/视图编程(一)——理论知识

    一 为什么需要MVC模型 视图 MVC模型 视图架构是用于实现对大量数据的存储 处理及其显示 如下图所示 没用MVC存储数据的是图片中的上半部分 用了MVC处理数据后的是图片的下半部分 1 MVC模型 视图的作用 a 模型 数据 与视图 显
  • 基于QT的串口调试助手制作

    目录 1 创建文件 2 串口界面制作 3 代码 4 结果 5 改进 6 百度链接 1 创建文件 创建带有ui界面的qt程序 可以点击文件 新建文件或项目就会出现上面的界面 选这Application 应用程序 创建项目 其中Qt Widge
  • 实验七 组合数据类型 Educoder

    实验七 组合数据类型 Educoder 组合数据类型是计算机编程中非常重要的概念之一 它允许我们将多个不同类型的数据组合在一起 形成一个新的数据类型 在本篇文章中 我将介绍组合数据类型的概念和用法 并通过一个示例源代码来展示它的应用 在讨论
  • “自顶向下,逐步求精”方法简要介绍

    什么是自顶向下 逐步求精 自顶向下 逐步求精 是结构化程序设计常见的思路 自顶向下 是将复杂 大的问题划分为小问题 找出问题的关键 重点所在 然后用精确的思维定性 定量地去描述问题 逐步求精 是将现实世界的问题经抽象转化为逻辑空间或求解空间
  • openwrt下crontab定时任务实现

    openwrt下crontab定时任务实现 我的需求是需要加个定时执行脚本来监控智能网关运行的进程 一旦网关进程异常关闭 就能立即把它拉起来 从而保证网关运行的可靠性 这里我们用到了crontab服务 OpenWRT系统默认已经加入了cro
  • 最大比例

    题目描述 解析 接下来就是求解k和p的过程 在这道题中很难使用欧几里得算法就求解最大公约数 因此尝试使用另一种方法 更相减损术 循环相减法 如果要使用欧几里得算法的话 就需要开一个非常复杂的根号 非常难算 代码 include
  • 【MyBatis】查询语句汇总

    定义一个Car类 封装汽车相关信息的 pojo类 public class Car 数据库表当中的字段应该和pojo类的属性一一对应 建议使用包装类 这样可以防止null的问题 private Long id private String
  • 大数据面试-07-大数据工程师面试题

    面试问题 1 从前到后从你教育背景 学过哪些课 到各个项目你负责的模块 问的很细 本以为他是物理学博士 但是所有的技术都懂 2 hadoop 的 namenode 宕机 怎么解决 先分析宕机后的损失 宕机后直接导致client无法访问 内存
  • Ubuntu暂停和恢复下载

    下载anaconda时用的官网地址 速度太慢 用ctrl c暂停了 从网上查找的恢复下载的方法有 wget c URL URL是下载网址 不是本地文件的路径 用本地路径时会出现Scheme missing
  • java ref out_ref与out之间的区别深入解析

    ref和out都是C 中的关键字 所实现的功能也差不多 都是指定一个参数按照引用传递 对于编译后的程序而言 它们之间没有任何区别 也就是说它们只有语法区别 总结起来 他们有如下语法区别 1 ref传进去的参数必须在调用前初始化 out不必
  • Github速度太慢全网最全方案

    Github速度太慢全网最全方案 近日 我在Github上下载源码 真的鸡肋 慢的一匹 通过以下方式 让我下载Github速度飞快 因为刚好有代理 就用的第一种方式 而后面几种方式参考自网上的一些方案 自己也尝试了一下 有点用 就贴出来 给
  • SpringBoot2+Netty+WebSocket(netty实现websocket)

    一 SpringBoot2 Netty WebSocket netty实现websocket 支持URL参数 原文链接 https zhengkai blog csdn net article details 91552993 思路 用ne