原文:《Netty入门之WebSocket初体验》
Netty简述
Netty是一个事件驱动型、异步非阻塞的高性能 Java IO 框架。
它对 Java NIO 框架作了封装与改进,提供了良好的稳定性和伸缩性,可简化网络应用的开发。很多项目将其作为底层Socket通信的基础框架(如,广为人知的Dubbo)。
原生 Java NIO 框架的使用门槛比较高,需要对Java多线程编程、网络编程等相关知识非常熟悉。
异步、非阻塞、情况多变的网络状况等特殊性引出的半包读写、失败缓存、客户端断连重连、网络拥塞、异常状态/内容的处理非常繁琐困难,非业务性的编码工作量大。
此外,Java NIO 框架各种类、API繁杂,学习成本也不小。
相对而言,Netty的使用比较容易。API简单,性能、可靠性、处理能力、可扩展性等各方法都提供了良好的支持,还避免了JDK NIO的bug。
Netty支持多种主流协议,预置了多种编解码功能,使用门槛较低。
WebSocket简述
WebSocket是H5提出的一个协议规范。它是一种为解决客户端与服务端实时通信而产生的技术。其本质是一种基于TCP的协议。客户端先以 HTTP/HTTPS 协议发出一条特殊的HTTP请求,与服务端握手建立连接后,双方可进行通信。
在WebSocket出现之前,Web交互多是基于HTTP协议(有短连接,也有长连接)。
WebSocket的优势:
-
节省通信开销。
以前实现“消息推送”/“即时通讯”都采用轮询机制。客户端会定期(如,每隔1秒)主动发起请求,从服务端拉取消息。可能实际需要的业务数据很少,而HTTP Header数据量占比较高,很耗服务器资源。
在WebSocket模式下,连接建立后,双方通信的非业务数据占比较少。 - 服务端主动推送数据给客户端。
- 实时通信。WebSocket连接建立后,双方可以相互推送信息,及时性比传统HTTP方式更高。
示例:一个简单的应答程序
网页客户界面示例
主要代码
引入对Netty的依赖(Maven)
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>5.0.0.Alpha2</version> </dependency>
配置类
public class Config { public static final String Service_Host = "localhost"; public static final int Service_Port = 6789; public static final String Service_Url = String.format( "ws://%s:%s/echo", Service_Host, Service_Port); }
客户端连接(Channel)处理类
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.stream.ChunkedWriteHandler; public class EchoChannelHandler extends ChannelInitializer { /** * 初始化客户端连接中的各组件 */ @Override protected void initChannel(SocketChannel channel) throws Exception { ChannelPipeline pipeline = channel.pipeline(); pipeline.addLast("http-codec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(4 * 1024));// 请求内容的最大长度:4KB pipeline.addLast("http-chunked", new ChunkedWriteHandler()); pipeline.addLast("handler", new EchoChannelInboundHandler()); } }
消息内容处理类
import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; public class EchoChannelInboundHandler extends SimpleChannelInboundHandler<Object> { private WebSocketServerHandshaker handshaker = null; /** * 用于格式化返回消息中的时间戳 */ private DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssZ").withZone(ZoneId.of("UTC")); /** * 处理客户端请求的核心方法 */ @Override protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { if (msg instanceof FullHttpRequest) { // 处理握手请求 handleHandshakeRequest(ctx, (FullHttpRequest) msg); } else if (msg instanceof WebSocketFrame) { // 处理WebSocket消息(在握手请求之后) handleWebSocketFrame(ctx, (WebSocketFrame) msg); } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); System.out.println(String.format("Client connected. [%s]", ctx.channel().remoteAddress())); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); System.out.println(String.format("Client disconnected. [%s]", ctx.channel().remoteAddress())); } /** * 处理握手请求 */ private void handleHandshakeRequest(ChannelHandlerContext ctx, FullHttpRequest req) { Channel channel = ctx.channel(); if (req.decoderResult().isSuccess() && "websocket".equals(req.headers().get("Upgrade"))) { // 解码成功,且请求的协议是WebSocket WebSocketServerHandshakerFactory handshakerFactory = new WebSocketServerHandshakerFactory(Config.Service_URL, null, false); handshaker = handshakerFactory.newHandshaker(req); } if (null == handshaker) { // 握手器创建设失败 WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel); } else { // 握手 handshaker.handshake(channel, req); } } /** * 处理WebSocket消息 */ private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { Channel channel = ctx.channel(); if (frame instanceof CloseWebSocketFrame) { // 处理关闭WebSocket的指令 frame.retain(); handshaker.close(channel, (CloseWebSocketFrame) frame); } else if (frame instanceof TextWebSocketFrame) { // 此服务只处理文本消息,所以排除非文本消息 // 返回应答消息 String requestContent = ((TextWebSocketFrame) frame).text(); System.out.println( String.format("Received [%s]: %s", channel.remoteAddress(), requestContent)); String responseContent = String.format("%s %s: %s", dateTimeFormatter.format(ZonedDateTime.now()), channel.id(), requestContent); channel.writeAndFlush(new TextWebSocketFrame(responseContent)); } } }
开启服务的主方法
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public static void main(String[] args) throws Exception { EventLoopGroup acceptorGroup = new NioEventLoopGroup(); EventLoopGroup clientGroup = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(acceptorGroup, clientGroup); bootstrap.channel(NioServerSocketChannel.class); bootstrap.childHandler(new EchoChannelHandler()); try { ChannelFuture channelFuture = bootstrap.bind(Config.Service_Host, Config.Service_Port); Channel channel = channelFuture.sync().channel(); System.out.println("Echo Service Started..."); channel.closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { // 优雅地退出程序 acceptorGroup.shutdownGracefully(); clientGroup.shutdownGracefully(); System.out.println("Echo Service Stopped."); } }
客户端网页
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>[Echo] Client</title> <script type="text/javascript"> var echoSocket; function initEchoSocket() { if (!window.WebSocket) { window.WebSocket = window.MozWebSocket; } if (!window.WebSocket) { var spStatus = document.getElementById("status"); spStatus.innerText = "Your browser does not support WebSocket."; } else { echoSocket = new WebSocket("ws://localhost:6789/echo"); echoSocket.onopen = function(event) { var spStatus = document.getElementById("status"); spStatus.innerText = "WebSocket is open. You can communicate with server now.\r\n"; }; echoSocket.onmessage = function(event) { var txtResponse = document.getElementById('responseContent'); txtResponse.value += event.data + "\r\n"; }; echoSocket.onclose = function(event) { var spStatus = document.getElementById("status"); spStatus.innerText = "WebSocket closed."; }; } } function sendToServer() { if (echoSocket && WebSocket.OPEN == echoSocket.readyState) { message = document.getElementById("message").value; echoSocket.send(message); } } function closeEchoSocket() { if (echoSocket && WebSocket.OPEN == echoSocket.readyState) { echoSocket.close(); } } initEchoSocket(); </script> </head> <body> <h1>Echo Service Sample</h1> <h2>WebSocket Status</h2> <p id="status">uninitialized</p> <input type="button" value="Open WebSocket" onclick="initEchoSocket()"/> <input type="button" value="Close WebSocket" onclick="closeEchoSocket()"/> <h2>Send to Server</h2> <input type="text" id="message" value=""/> <input type="button" value="Send" onclick="sendToServer()"/> <h2>Echo from Server:</h2> <textarea id="responseContent" style="width:400px; height:300px;"></textarea> </body> </html>
相关推荐
孙允中临证实践录.pdf
Rqalpha-myquant-learning对开源项目Rqalpha的改造,在应用上面更适合个人的应用。学习量化策略,对量化策略进行开发调试。2018-05-25程序更新集成大鱼金融提供的分钟线回测Mod,用来提供Jaqs分钟线数据源,测试程序通过。目前的改造情况1.增加ats.main.py,来驱动起回测,使程序可以使用pycharm进行开发调试2.增加批量回测功能3.在AlgoTradeConfig中进行配置回测的策略和所需要的参数信息,参数信息通过excel文件进行配置4.在ats.main.py中设置参数为batch,运行回测,会将输出的.csv文件放在cvsResult目录下,将回测的图片保存在picResult目录下。5.读取回测的.csv文件,提取账户信息,可以将不同参数回测的结果输出在同一张图片上,更加清晰的看清同一个策略,不同参数所带来的变化。6.从广发信号站点获取历史交易信号(站点已停止,此处无法继续)7.增加通用函数的封装,现阶段增加了对TA_LIB的调用封装(未完整完成)8.增加了对增量资金定投的情况的模拟,用
航班背景随着国内民航的不断发展,航空出行已经成为人们比较普遍的出行方式,但是航班延误却成为旅客们比较头疼的问题。台风,雾霾或飞机故障等因素都有可能导致大面积航班延误的情况。大面积延误给旅客出行带来很多不便,如何在计划起飞前2小时预测航班延误情况,让出行旅客更好的规划出行方式,成为一个重大课题。要求提前2小时(航班计划起飞时间前2小时),预测航班是否会延误3小时以上(给出延误3小时以上的概率)
comsol变压器绝缘油中流注放电仿真,使用PDE模块建立MIT飘逸扩散模型。 模型到手即用,提供MIT鼻祖lunwen中文版,及相关学习笔记资料。 流注放电,绝缘油,油纸绝缘。
基于STM8单片机的编程实例,可供参考学习使用,希望对你有所帮助
云南大数据交通太阳的云南大数据交通
comsol激光打孔(不通)水平集两相流仿真模型,涉及温度场流场水平集, 模型为复现模型,仅供学习,可自己更材料功率等参数 爽快确认模型无误并收送变形几何三维打孔模型或水平集抛光模型。
哈工深 自适应滤波课堂笔记
Django框架实现学生信息管理系统 总体概括 注册流程 首先进行输入用户名(邮箱)、密码以及验证码,输入完之后点击注册按钮。如果输入的不正确,提示错误信息。 如果一切信息填写正确无误,调用STMP模块发送激活邮件,用户必须要点击接收到邮箱链接,进行邮件激活后才方可登陆。 即使注册成功,没有激活的用户也不能登陆,用户以get的方式直接重定向到注册页面。 注册登录: 用户能在系统中进行登陆注册和忘记密码进行找回的功能。 个人中心:修改头像,修改密码,修改邮箱,可以看到我的信息。 日志记录: 记录后台人员的操作,方便发现BUG和查看各项调用进行时间。 导航栏:学生信息中有基本信息、年级及成绩信息的模块,能够排序筛选等功能。 多选操作: 可以选择多条记录进行删除操作,还可以在课程列表页可以对不同课程进行排序。 数据页码: 可以设置各项数据在每一页中显示的数量多少,进行翻页功能。 模块列表页: 能够有过滤器功能,在范围内进行查看数据。还能将数据导出为csv,x
车辆主动悬架防侧翻控制 利用Simulink和Carsim进行联合仿真,搭建主动悬架以及防倾杆模型,在不同转角工况下进行仿真试验,设置滑模等控制器计算维持车辆侧倾稳定性所需的力矩,将力矩分配到各个悬架实现控制效果。 控制效果良好,保证运行成功。 项目报告撰写请单独。
计算机系毕业设计
资源描述: HTML5实现好看的MT外卖订餐网站源码,好看的酷炫的MT外卖订餐网站源码,酷炫的MT外卖订餐网站源码模板,HTML酷炫的MT外卖订餐网站源码,内置酷炫的动画,界面干净整洁,页面主题,全方位介绍内容,可以拆分多个想要的页面,可以扩展自己想要的,注释完整,代码规范,各种风格都有,代码上手简单,代码独立,可以直接运行使用。也可直接预览效果。 资源使用: 点击 index.html 直接查看效果
MDPI下的sensors模板,.docx格式
新医林改错《内经·素问》分册.pdf
命令行查看基金、个股数据,使用天天基金和新浪财经的数据接口,欢迎大家fork基金2.0命令行查看基金、个股数据,使用天天基金和新浪财经的数据接口,欢迎大家fork环境准备运行环境Python3 所需的软件包requests prettytable colorama基金自选修改my_jijin.txt文本文件,每行都是一个您关注的基金代码启动方式python3 main.py
NiuCloud-Admin-SAAS 是一款快速开发SaaS通用管理系统后台框架, 前端采用最新的技术栈Vite+TypeScript+Vue3+ElementPlus最流行技术架构,后台结合PHP8、Java SDK、Python等主流后端语言搭建是一款快速可以开发企业级应用的软件系统。
脉振方波高频注入代码+增强型滑膜esmo代码,永磁同步电机高频注入程序 资料为C代码一份,大厂代码,可运行,经典流传; 配套一篇代码对应的说明文档,详细算法说明; 脉振方波注入方法相对于脉振正弦信号注入的形式,信号处理的过程少了一些滤波器 ,计算更简单,并且由于信号频段较高,可以实现更高的动态响应能力。 增强滑膜控制;
逆变器下垂控制,微电网逆变器孤岛下垂控制,波形输出完美
multisim学习电路基础视频共42讲(个人觉得超赞)5G提取方式是百度网盘分享地址
基于 Vue 数据可视化组件(类似阿里DataV,大屏数据展示)编辑器。基于 Vue 数据可视化组件(类似阿里DataV,大屏数据展示)编辑器。项目初始化npm install启动开发环境npm run dev项目打包上线npm run buildLints和fixes文件npm run lint