原文:《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>
相关推荐
**SpringBoot+Netty+WebSocket+Redis:构建分布式实时聊天应用** 在当今的互联网世界中,实时通信和数据共享是许多应用程序的核心需求。Spring Boot作为一款流行的Java框架,因其简洁的配置和快速开发特性而备受...
1、基于netty+websocket+springboot的实时聊天系统项目源码.zip 2、该资源包括项目的全部源码,下载可以直接使用! 3、本项目适合作为计算机、数学、电子信息等专业的课程设计、期末大作业和毕设项目,作为参考资料...
在现代Web开发中,实时通信已经成为一个不可或缺的功能,Spring、Netty和WebSocket的结合为构建高性能、低延迟的实时应用提供了强大的解决方案。本实例将详细探讨如何利用这三种技术进行集成,实现高效的双向通信。 ...
在"Spring Boot 整合 Netty + WebSocket 实时消息推送"项目中,我们主要关注以下几个核心知识点: 1. **Spring Boot集成WebSocket**:Spring Boot提供了Spring WebSocket模块,可以方便地集成WebSocket功能。通过...
Netty和WebSocket是现代网络应用开发中的两个重要技术,它们结合使用可以构建高效、实时的双向通信聊天室。本文将详细介绍这两个技术以及如何利用它们创建一个支持私聊功能的聊天室。 **Netty简介** Netty是一个高...
在本文中,我们将深入探讨如何利用 Netty 和 WebSocket 实现心跳检测和断线重连机制。 首先,我们需要理解 WebSocket 协议。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它为客户端和服务器提供了低...
文章的对应博客https://blog.csdn.net/weixin_43333483/article/details/127716359#comments_25224363 启动后访问:http://localhost:8088/ 就可开始进行聊天
毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+springboot的实时聊天系统 毕设项目:基于netty+websocket+...
在本文中,我们将深入探讨如何利用 Netty 和 WebSocket 技术实现通信,以及 `callServer` 文件可能包含的内容。 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它为 Web 应用程序提供了低延迟、双向通信...
netty+websocket在线聊天室:基于Java构建,包含282个文件,其中包括205个GIF图像、29个JavaScript文件、12个Java类文件、11个CSS样式文件、4个JPG图像、3个HTML文件、3个PNG图像、3个FreeMarker模板文件(.ftl)、2...
本项目“springboot+netty+webSocket实现在线聊天”就是针对这一需求提供的一种解决方案。它利用Spring Boot的便利性、Netty的高性能以及WebSocket的双向通信能力,创建了一个高效、稳定的实时聊天平台。 **Spring ...
基于springboot+mybatis+druid+swagger+netty+websocket的即时通讯工程 ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工...
springBoot+webSocket+uniapp实现实时聊天功能
本项目围绕“`spring+netty+websocket`高并发聊天室”这一主题,旨在实现一个能够处理大量并发连接的聊天应用。`Tomcat` 作为主流的 Java Web 服务器,通常用于部署基于 `Spring` 的应用程序,而 `Netty` 是一个高...
在本文中,我们将深入探讨如何利用 Netty 实现基于 WebSocket 的通信服务。 WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它为 Web 应用程序提供低延迟、双向通信的能力。WebSocket API 设计为与 HTTP ...
基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 基于springboot+netty+vue构建的类似bililbili的弹幕群聊系统,个人娱乐项目,可用于前后端开发学习研究 ...
【标题】:“基于netty+websocket的在线聊天室”指的是使用Netty作为网络通信框架,结合WebSocket协议实现的实时通信应用。Netty是Java领域的一个高性能、异步事件驱动的网络应用程序框架,常用于开发高并发、低延迟...