Netty网络框架学习教程
2024/10/21 23:03:16
本文主要是介绍Netty网络框架学习教程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
本文基于 Netty 4.1.68.Final 版本,详细介绍 Netty 的特点、优势以及应用场景,并提供了详细的 Netty 网络框架学习指南,帮助读者快速掌握 Netty 的核心概念和实战案例。学习内容涵盖环境搭建、核心概念详解、实战案例及性能优化策略等内容。
Netty简介
Netty 是一个高性能、异步事件驱动的网络应用框架,用于快速开发可维护的网络应用程序。Netty 能够高效地处理各种协议,包括 HTTP、WebSocket、TCP、UDP 等。它广泛应用于服务器端和客户端的网络编程,支持多种编程语言和平台。Netty 的设计目标是简化网络编程,提高开发效率,同时保持高性能和稳定性。
Netty的特点和优势
- 高性能:Netty 使用高效的内存管理机制,能够快速处理大量的网络请求,减少资源消耗。
- 异步非阻塞:Netty 使用异步非阻塞 I/O 模型,结合事件驱动模型,能够高效处理并发请求。
- 灵活性:Netty 支持多种协议和传输类型,便于开发人员根据需求灵活选择。
- 易用性:Netty 提供了丰富的 API 和工具,使开发人员能够快速实现网络通信功能。
- 可扩展性:Netty 的模块化设计使开发人员可以轻松扩展功能,满足复杂业务需求。
- 内存管理:Netty 使用零拷贝技术,有效减少内存使用,提高数据传输效率。
Netty的应用场景
- 高性能服务器:在高并发环境下,Netty 能够实现高效的数据传输。
- 网络游戏:网络游戏需要实时通信和高并发处理,Netty 能够满足这些需求。
- 移动应用后端:移动应用的后端服务需要快速响应客户端请求,Netty 是理想的选择。
- 大数据传输:在大数据传输场景下,Netty 能够高效处理大量数据。
- 分布式系统:Netty 支持多种协议和传输类型,适用于构建分布式系统。
- Websocket 和 HTTP 服务:Netty 支持 Websocket 和 HTTP 服务,简化了相关应用的开发。
Netty环境搭建
准备开发环境
首先,确保安装了 Java 开发环境。Netty 是基于 Java 的,因此需要安装 Java 开发工具包(JDK)。可以通过官方网站下载 JDK,并按照官方文档进行安装。另外,也可以使用 Maven 或 Gradle 等构建工具来管理项目依赖。
下载和安装Netty
下载 Netty 的最新版本可以从 Maven 中央仓库获取。Maven 会自动从中央仓库下载 Netty 的依赖。如果使用 Gradle,也可以通过配置 Gradle 依赖来下载 Netty。
<dependencies> <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.68.Final</version> </dependency> </dependencies>
创建第一个Netty应用
创建一个简单的 Netty 应用程序,实现一个 TCP 服务器,监听客户端连接并接收消息。
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
Netty核心概念详解
Channel和ChannelHandler
在 Netty 中,Channel 表示一个网络连接,可以是 TCP 连接、UDP 连接等。Channel 是双向的,可以读也可以写。每个 Channel 都有一个 ChannelPipeline,用于处理接收到的数据。ChannelHandler 是处理数据的核心类,ChannelPipeline 中可以添加多个 ChannelHandler,每个 ChannelHandler 负责处理特定的任务。
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Client"); } }
EventLoop和EventLoopGroup
EventLoop 是 Netty 的核心组件之一,它负责处理 I/O 事件(如新的连接、读、写、关闭事件等)。每个 Channel 都关联一个 EventLoop,EventLoop 负责处理该 Channel 上的所有 I/O 事件。EventLoopGroup 是一个 EventLoop 的集合,通常用于管理多个 EventLoop,使得多个 Channel 可以复用 EventLoop。
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Client"); } }
Bootstrap和ServerBootstrap
Bootstrap 和 ServerBootstrap 是 Netty 提供的用于快速启动客户端和服务端的工具类。它们封装了复杂的启动逻辑,简化了应用程序的启动过程。其中,ServerBootstrap 用于启动服务端,Bootstrap 用于启动客户端。
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Client"); } }
编解码器Codec
Netty 提供了丰富的编解码器,用于处理不同格式的数据。常见的编解码器包括 LengthFieldPrepender、LengthFieldBasedFrameDecoder、StringDecoder、StringEncoder 等。这些编解码器可以组合使用,以应对复杂的网络通信场景。
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4)); ch.pipeline().addLast(new LengthFieldPrepender(4)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Client"); } }
Netty实战案例
实现简单的TCP通信
下面是一个简单的 TCP 通信示例,实现一个服务端和客户端进行消息交互。
服务端代码:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Client"); } }
客户端代码:
import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyClient { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.TCP_NODELAY, true); ChannelFuture f = b.connect("localhost", 8080).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Server"); } }
实现异步HTTP服务器
下面是一个简单的异步 HTTP 服务器示例,实现一个 HTTP 服务器,能够处理客户端的 HTTP 请求。
服务端代码:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyHttpServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new HttpObjectAggregator(65536)); ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); ch.pipeline().addLast(new MyHttpHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class MyHttpHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { if (msg instanceof io.netty.handler.codec.http.HttpRequest) { io.netty.handler.codec.http.HttpRequest request = (io.netty.handler.codec.http.HttpRequest) msg; System.out.println("Received HTTP request: " + request); ctx.writeAndFlush(new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK)); } } }
客户端代码:
import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; public class NettyHttpClient { public static void main(String[] args) throws Exception { URL url = new URL("http://localhost:8080"); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String inputLine; StringBuilder content = new StringBuilder(); while ((inputLine = in.readLine()) != null) { content.append(inputLine); } in.close(); System.out.println("Received HTTP response: " + content.toString()); } }
处理粘包和拆包问题
在处理网络通信时,经常会出现粘包和拆包问题。粘包是指多个消息被当作一个消息处理,拆包是指一个消息被拆分成多个部分进行处理。Netty 提供了 LengthFieldBasedFrameDecoder 等编解码器来解决这些问题。
处理粘包和拆包问题的实例:
服务端代码:
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyServer { public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4)); ch.pipeline().addLast(new LengthFieldPrepender(4)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); ChannelFuture f = b.bind(8080).sync(); f.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Client"); } }
客户端代码:
import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; public class NettyClient { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1048576, 0, 4, 0, 4)); ch.pipeline().addLast(new LengthFieldPrepender(4)); ch.pipeline().addLast(new StringDecoder()); ch.pipeline().addLast(new StringEncoder()); ch.pipeline().addLast(new MyChannelHandler()); } }) .option(ChannelOption.TCP_NODELAY, true); ChannelFuture f = b.connect("localhost", 8080).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } } class MyChannelHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { System.out.println("Received: " + msg); ctx.writeAndFlush("Hello, Server"); } }
Netty性能优化
常见的性能瓶颈
- 资源耗尽:在高并发情况下,可能会出现资源耗尽的情况,如线程池资源耗尽。
- 网络拥塞:如果网络带宽不足,可能导致数据传输速度慢。
- CPU瓶颈:高并发情况下,可能会出现 CPU 资源耗尽的情况。
- 内存泄露:长时间运行的网络应用可能会出现内存泄露。
- 垃圾回收:频繁的垃圾回收操作会影响性能。
性能优化策略
- 增加线程池资源:适当增加线程池资源,避免线程池资源耗尽。
- 优化网络带宽:优化网络带宽,提高数据传输速度。
- 减少 CPU 负载:优化代码,减少 CPU 负载。
- 合理分配内存:合理分配内存,避免内存泄露。
- 减少垃圾回收:优化代码,减少垃圾回收操作。
实践中的注意事项
- 合理设置线程池大小:设置合理的线程池大小,避免资源耗尽。
- 减少不必要的 I/O 操作:减少不必要的 I/O 操作,提高性能。
- 使用缓存机制:使用缓存机制,减少数据传输。
- 避免内存泄漏:避免内存泄漏,定期检查内存使用。
- 优化数据结构:优化数据结构,提高数据处理速度。
通过以上实践,可以有效地优化 Netty 的性能,提高应用程序的响应速度和稳定性。
这篇关于Netty网络框架学习教程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-15JavaMailSender是什么,怎么使用?-icode9专业技术文章分享
- 2024-11-15JWT 用户校验学习:从入门到实践
- 2024-11-15Nest学习:新手入门全面指南
- 2024-11-15RestfulAPI学习:新手入门指南
- 2024-11-15Server Component学习:入门教程与实践指南
- 2024-11-15动态路由入门:新手必读指南
- 2024-11-15JWT 用户校验入门:轻松掌握JWT认证基础
- 2024-11-15Nest后端开发入门指南
- 2024-11-15Nest后端开发入门教程
- 2024-11-15RestfulAPI入门:新手快速上手指南