原文:《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>
相关推荐
少儿编程scratch项目源代码文件案例素材-纸人伙计.zip
scratch少儿编程逻辑思维游戏源码-忍者罗伊 V5.zip
scratch少儿编程逻辑思维游戏源码-跑和枪.zip
前端开发_基于jQuery和EasyUI框架_企业级Web应用UI组件库与后台管理系统模板_提供GPL开源版本和商业授权版本的双重授权模式_适用于快速构建响应式管理后台和复杂数据可
少儿编程scratch项目源代码文件案例素材-纸格通关 云变量.zip
微信机器人开发_Wechaty框架_百度云主机部署_自然语言处理_消息自动化处理_多媒体文件管理_聊天记录持久化_表情包导出_语音视频自动保存_文件管理系统集成_跨平台数据同步_个
少儿编程scratch项目源代码文件案例素材-钻机机器人.zip
少儿编程scratch项目源代码文件案例素材-作战基地.zip
云计算_微服务分布式架构SpringCloudSpringBootDubboVuejs_互联网云快速开发框架敏捷开发系统代码生成工作流CMS图表统计地图统计_免费开源JAVA企业
scratch少儿编程逻辑思维游戏源码-日落塔.zip
Tobapuww_GPT-Recovery-Files_12888_1745866661386
少儿编程scratch项目源代码文件案例素材-战斗竞技场.zip
scratch少儿编程逻辑思维游戏源码-球球大作战.zip
聚合支付系统/官方个人免签系统/三方支付系统稳定安全高并发 附教程 系统采用FastAdmin框架独立全新开发,安全稳定,系统支持代理、商户、码商等业务逻辑。 针对最近一些JD,TB等业务定制,子账号业务逻辑API 非常详细,方便内置对接! IP白名单 业务逻辑 支持IP白名单,黑名单,全局白名单,全局黑名单,保障系统的安全。 接口验签名 采用支付宝RSA加密接口方式,防止篡改数据,导致对账困难,资金大量损失,无故少钱 对接灵活 全部对接参数灵活操作 风控完善 轮询、交易金额、随机金额、最大金额、最小金额等 测试环境: Nginx+PHP7.0+MySQL5.6 网站运行目录:/public 伪静态设置为:thinkphp规则 数据库信息修改路径:/application/database.php
校园社交服务_微信小程序云开发_公告资讯失物招领二手交易兼职招聘表白墙_为高校师生提供一站式校园生活服务平台包含校园动态通知课程表查询失物发布与认领二手物品交易平台兼职信息发布与求
yinghuayu2377_myFTPDemo_32152_1745866651913
scratch少儿编程逻辑思维游戏源码-魔法球.zip
idea导入个人风格
少儿编程scratch项目源代码文件案例素材-找袜子.zip
Web开发_Go语言_Gin框架_GORM_MySQL_Vue3_ElementPlus_代码生成_SaaS架构_企业级_后台管理系统_快速开发框架_的卢全功能版_基于Golang