SpringBoot2+Netty+WebSocket(netty实现websocket,支持URL参数)
关于Netty
Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。
更新
- 2019-7-11 新增URL参数支持,并解决了带参URL导致的连接自动断开问题,感谢大家的支持。
MAVEN依赖
<dependencies><!-- https://mvnrepository.com/artifact/io.netty/netty-all --><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.36.Final</version></dependency></dependencies>
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());}}
}
NettyServer
启动的NettyServer,这里进行配置
/*** 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>() { // 绑定客户端连接时候触发操作@Overrideprotected 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>() { // 绑定客户端连接时候触发操作@Overrideprotected 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.netvar 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;}}
效果演示
思路优化
由于netty
不能像默认的websocket一样设置一些PathVariable
例如{uid}
等参数(暂未发现可以,如果有发现欢迎补充
),所以很多时候发送到后台的报文可以设置一些特殊的格式,例如上文的004401:大家好
,可以分解为userid:text
,当然userid也可以是加密的一些报文,甚至可以学习其他报文一样设置加密区,这取决于大家的业务需要. (已更新解决方案)
后言
项目已经整合进开源项目spring-cloud-study的子模块spring-cloud-study-netty-websocket,作为对websocket体系的补充,对SpringBoot2.0集成WebSocket,实现后台向前端推送信息 的完善。
改造netty支持url参数
最新改造的项目代码已经上传,克服了使用url会导致连接断开的问题,详情请看spring-cloud-study
- 首先,调整一下加载
handler的顺序
,优先MyWebSocketHandler
在WebSocketServerProtocolHandler之上。
ch.pipeline().addLast(new MyWebSocketHandler());
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));
- 其次,改造
MyWebSocketHandler
的channelRead
方法,首次连接会是一个FullHttpRequest
类型,可以通过FullHttpRequest.uri()获取完整ws的URL地址,之后接受信息的话,会是一个TextWebSocketFrame
类型。
public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("与客户端建立连接,通道开启!");//添加到channelGroup通道组MyChannelHandlerPool.channelGroup.add(ctx.channel());}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {System.out.println("与客户端断开连接,通道关闭!");//添加到channelGroup 通道组MyChannelHandlerPool.channelGroup.remove(ctx.channel());}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//首次连接是FullHttpRequest,处理参数 by zhengkai.blog.csdn.netif (null != msg && msg instanceof FullHttpRequest) {FullHttpRequest request = (FullHttpRequest) msg;String uri = request.uri();Map paramMap=getUrlParams(uri);System.out.println("接收到的参数是:"+JSON.toJSONString(paramMap));//如果url包含参数,需要处理if(uri.contains("?")){String newUri=uri.substring(0,uri.indexOf("?"));System.out.println(newUri);request.setUri(newUri);}}else if(msg instanceof TextWebSocketFrame){//正常的TEXT消息类型TextWebSocketFrame frame=(TextWebSocketFrame)msg;System.out.println("客户端收到服务器数据:" +frame.text());sendAllMessage(frame.text());}super.channelRead(ctx, msg);}@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {}private void sendAllMessage(String message){//收到信息后,群发给所有channelMyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(message));}private static Map getUrlParams(String url){Map<String,String> map = new HashMap<>();url = url.replace("?",";");if (!url.contains(";")){return map;}if (url.split(";").length > 0){String[] arr = url.split(";")[1].split("&");for (String s : arr){String key = s.split("=")[0];String value = s.split("=")[1];map.put(key,value);}return map;}else{return map;}}
}
- html中的ws地址也进行改造
socket = new WebSocket("ws://127.0.0.1:12345/ws?uid=666&gid=777");
- 改造后控制台输出情况
收到新连接
与客户端建立连接,通道开启!
接收到的参数是:{"uid":"666","gid":"777"}
/ws
客户端收到服务器数据:142531:这里输入消息
客户端收到服务器数据:142531:这里输入消息
客户端收到服务器数据:142531:这里输入消息
failed: WebSocket opening handshake timed out
听说是ssl wss的情况下才会出现,来自 @around-gao 的解决方法:
把MyWebSocketHandler和WebSocketServerProtocolHandler调下顺序就好了。
SpringBoot2+Netty+WebSocket(netty实现websocket,支持URL参数)相关推荐
- SpringBoot2+Netty+WebSocket(netty实现websocket)
##### 一.SpringBoot2+Netty+WebSocket(netty实现websocket,支持URL参数) 原文链接: https://zhengkai.blog.csdn.net/a ...
- springboot2.3手册:5分钟用Netty搭建高性能异步WebSocket服务
溪云阁:专注编程教学,架构,JAVA,Python,微服务,机器学习等领域,欢迎关注,一起学习. 断更快两个月了,6月份工作忙到飞起,7月份家里又有事,已经累到躺下就想睡觉的程度了. 现在我们做Web ...
- 基于netty实现一个简单的支持http和webSocket协议的的服务器(含xxl-job通信模块源码分析)
文章目录 背景 依赖 包结构 实现 WebSocketServer 业务handler WebSocketServerHandler 测试 xxl-job 源码中基于netty实现的http 总结 参 ...
- maven netty 配置_springboot2.3手册:5分钟用Netty搭建高性能异步WebSocket服务
互联网应用架构:专注编程教学,架构,JAVA,Python,微服务,机器学习等领域,欢迎关注,一起学习. 断更快两个月了,6月份工作忙到飞起,7月份家里又有事,已经累到躺下就想睡觉的程度了. 现在我们 ...
- 基于Netty最简单的WebSocket通讯
基于Netty最简单的WebSocket通讯 基于Netty最简单的WebSocket通讯 总览 服务端 EasyWsServer EasyWsServerHandler 客户端 EasyWsClie ...
- Springboot实战:Springboot+Netty优雅的创建websocket客户端 (附源码下载)
Springboot-cli 开发脚手架系列 Netty系列:Springboot+Netty优雅的创建websocket客户端 (附源码下载) 文章目录 Springboot-cli 开发脚手架系列 ...
- Netty权威指南之Websocket协议开发
本章主要学习内容如下: 1.HTTP协议弊端 2.WebSocket入门 3.Netty WebSocket协议开发 第一节:HTTP协议弊端 将HTTP协议的主要弊端总结如下: 1.HTTP协议为半 ...
- Netty 的 ByteBuf 是如何支持 堆内存非池化 实现的
Netty的ByteBuf是如何支持堆内存非池化实现的 ByteBuffer 从实现方式上分成 HeapByteBuffer 和 DirectByteBuffer 两种内存实现方式, HeapByte ...
- websocket测试工具,支持ws wss服务端和客户端
介绍一个名为:WebsocketMan的websocket测试工具,支持ws wss服务端和客户端.可以将请求保存为文件,支持header非常方便测试.支持Windows Linux macOS系统. ...
最新文章
- Ajax Upload多文件上传插件翻译及中文演示
- java轻量级IOC框架Guice
- 给vim添加自动跳出括号的功能
- redhat配置dns服务器bind
- EOF是什么?(转)
- linux-git服务搭建
- mac上搭建vue环境及webstorm新建vue项目
- 给创业者的30条建议
- HDU1114 Piggy-Bank 完全背包
- 云计算学习路线图课件:云计算中的常见的云配置错误
- this 的4种绑定机制
- 我的HTML学习之路03
- OpenCV精进之路(四):图像处理——图片的缩放和图像金字塔
- NYOJ113 - 字符串替换
- 【开源.NET】 分享一个前后端分离的轻量级内容管理框架
- 将mp3格式的音频转换为采样率8k的wav
- JS网页恶搞代码,不断弹出、关不掉的对话框
- LoadRunner详细使用教程
- html想实现文字环绕图片,HTML/CSS实现文字环绕图片布局
- 安装office 错误代码:30068-39
热门文章
- 深度学习公开语音识别数据集下载 | 论文下载|音频数据集|corpus ——简记
- 2021电工杯B题建模思路代码完整版
- 银行服务器销售业务,利用呼叫中心进行远程销售
- 自己留着用的 .net 图片水印 方法
- AI人工智能技术可以应用在网站seo优化推广上吗?
- echarts折线图点击x值动态高亮且显示tooltip
- 分布式数据库同步系统之Otter
- linux系统怎么两个盘对拷,Linux分区对拷等小技巧
- [php] 获取请求 IP 地址,及所处 IP 所在服务商代码
- 网址中请求参数中%2c