当前位置: 首页 > news >正文

构建高性能网络服务:从 Socket 原理到 Netty 应用实践

1. 引言

在 Java 网络编程中,Socket 是实现网络通信的基础(可以查看我的上一篇博客)。它封装了 TCP/IP 协议栈,提供了底层通信的核心能力。而 Netty 是在 Socket 和 NIO 的基础上,进一步封装的高性能、异步事件驱动的网络框架,简化了复杂的网络编程。

为什么需要学习 Socket?
学习 Netty 之前,理解 Socket 的基本原理(如 TCP 三次握手、四次挥手、阻塞和非阻塞 I/O 模型等)是必不可少的。Netty 的优势在于简化了这些底层细节,但要灵活掌握 Netty,我们需要具备对 Socket 的基本认识。可以参考我的上一篇博客,其中详细讲解了以下内容:

  • Socket 基础:什么是 Socket?它在 TCP/IP 协议中的角色。
  • Socket 通信模型:阻塞式 I/O 和非阻塞式 I/O 的区别与使用场景。
  • 多线程并发 Socket 服务器实现:从简单的 Echo 服务到并发服务器的完整代码示例。
  • TCP 粘包与拆包问题:及其在 Socket 编程中的解决方法。

Netty 的核心优势

  1. 简化开发流程
    • 不需要手动管理 Selector、Channel 等底层细节。
    • 内置多种编解码器,屏蔽二进制数据读写的复杂性。
  2. 性能优异
    • 高并发处理能力,通过事件循环机制轻松处理大量连接请求。
    • 支持零拷贝技术和内存池,减少内存复制,提高吞吐量。
  3. 解决常见问题
    • 轻松解决粘包与拆包问题,提供多种解码器如 FixedLengthFrameDecoderDelimiterBasedFrameDecoder 等。
    • 提供灵活的 ByteBuf 管理,简化内存管理操作。
  4. 功能强大且可扩展
    • 提供模块化架构,可轻松扩展功能,如自定义协议、日志监控等。
    • 支持多种协议(如 HTTP、WebSocket、UDP),满足不同场景需求。
  5. 被广泛验证的可靠性
    Netty 已被数百个商业项目验证,并成为分布式系统的基础组件,例如:
    • gRPC、Dubbo:主流分布式通信框架。
    • RocketMQ:高性能分布式消息队列。
    • Spring WebFlux:提供异步非阻塞的 HTTP 通信能力。
  6. 易用性强
    • Netty 提供了简洁直观的 API,使开发者能够专注于业务逻辑实现,而无需关心复杂的 I/O 细节。

典型应用场景

  • 高并发服务器:如 HTTP 服务器、网关服务。
  • 即时通讯系统:如在线聊天室、IM 应用等。
  • 文件传输系统:如大文件上传、下载服务。
  • 物联网通信:用于大规模设备间的数据上报与控制。

综上所述,Netty 是目前最流行的 NIO 框架,其健壮性、性能、可定制性和可扩展性在同类框架中首屈一指。学习 Netty 不仅需要理解其 API,还需要掌握其底层的事件驱动模型和线程池机制。通过本文的学习,我们将从 Netty 基础入门到拆包粘包问题示例,深入探索如何构建高性能的网络应用程序。

Netty架构图:

在这里插入图片描述

Netty特性:

在这里插入图片描述

4. Netty 快速入门示例

4.1 Netty 实现通信的步骤

Netty 客户端和服务器端的实现步骤基本一致,以下为实现通信的典型流程:

  1. 创建两个 NIO 线程组
    • 一个线程组专门用于处理网络事件(接收客户端连接)。
    • 另一个线程组进行网络通信中的读写操作。
  2. 创建 ServerBootstrap(服务端)或 Bootstrap(客户端)对象
    • 这是 Netty 启动引导类,用于配置 Netty 的一系列参数,例如接收/发送缓冲区大小等。
  3. 创建 ChannelInitializer
    • 该类用于进行 Channel 的初始化配置,如设置字符集、数据格式、处理数据的 Handler
  4. 绑定端口(服务端)或连接地址和端口(客户端):
    • 使用 bind() 方法绑定端口,connect() 方法进行连接。
    • 使用 sync() 同步阻塞方法等待服务器端启动完成。

Netty的使用非常简单,仅仅引入依赖即可快速开始:

对于 Java 8 项目,Netty 版本建议选择 4.1.94.Final,该版本为长期维护版(LTS),稳定性较高。

<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.94.Final</version>
</dependency>


4.2 服务端示例

以下是完整的 Netty 服务端代码示例,展示了如何快速搭建一个简单的网络服务:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;public class NettyServer {public static void main(String[] args) throws InterruptedException {// 创建两个 NIO 线程组EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 处理客户端连接EventLoopGroup workerGroup = new NioEventLoopGroup();  // 进行读写操作try {// 创建 ServerBootstrap 对象,配置 Netty 参数ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)  // 指定 NIO 传输方式.childHandler(new ChannelInitializer<SocketChannel>() {  // 初始化 Handler@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new NettyServerHandler());  // 添加处理器}});// 绑定端口,并同步等待启动ChannelFuture future = bootstrap.bind(8080).sync();System.out.println("服务器启动成功,端口:8080");// 阻塞等待关闭future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();  // 优雅关闭线程组workerGroup.shutdownGracefully();}}
}

说明

  • NioServerSocketChannel:使用 NIO 方式接收客户端连接。
  • NettyServerHandler:自定义数据处理器,用于处理客户端发送的数据。

4.3 客户端示例

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;public class NettyClient {public static void main(String[] args) throws InterruptedException {// 创建 NIO 线程组EventLoopGroup group = new NioEventLoopGroup();try {// 创建 Bootstrap 对象Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class)  // 使用 NIO 通道类型.handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) {ch.pipeline().addLast(new NettyClientHandler());  // 添加客户端 Handler}});// 连接到服务器ChannelFuture future = bootstrap.connect("localhost", 8080).sync();System.out.println("客户端已连接到服务器");future.channel().closeFuture().sync();} finally {group.shutdownGracefully();  // 优雅关闭线程组}}
}

说明

  • NioSocketChannel:NIO 传输方式,用于客户端连接。
  • NettyClientHandler:客户端处理器,用于发送数据或接收服务器响应。

4.4 关键类说明

  • ServerBootstrap / Bootstrap:Netty 启动引导类,配置线程组、处理器等。
  • EventLoopGroup:线程组,管理 I/O 线程池,处理网络事件。
  • ChannelInitializer:初始化 Channel,设置 Handler(如 NettyServerHandler)。
  • ChannelHandler:用于定义数据的处理逻辑。

4.5 完整运行步骤

  1. 引入依赖:
    • 添加 Netty 依赖到项目的 pom.xml 中。
  2. 编写 Netty 服务端代码,启动服务器监听端口 8080
  3. 编写 Netty 客户端代码,连接到服务器。
  4. 客户端发送数据,服务器接收并回显。

输出示例

服务器端输出

服务器启动成功,端口:8080
新客户端已连接:/127.0.0.1
收到客户端消息:Hello Netty

客户端输出

客户端已连接到服务器
收到服务器响应:Hello Netty

通过引入简单的依赖配置和少量代码,即可快速搭建基于 Netty 的高效网络通信服务,帮助我们轻松实现高并发、高性能的网络应用。


5. Netty 主要 API 介绍

在使用 Netty 时,理解其核心组件和常用方法是至关重要的。这些组件和方法构成了 Netty 框架的基础,帮助开发者灵活地进行网络应用开发。以下是对 Netty 核心组件及常用方法的详细说明。


5.1 核心组件

1. EventLoopGroup(事件循环组)

EventLoopGroup 是 Netty 的线程组接口,主要用于管理线程池,负责处理 I/O 事件和任务调度。Netty 使用了事件循环机制,通过 EventLoopGroup 来管理多个 EventLoop,每个 EventLoop 绑定到一个 Channel,以单线程方式处理 I/O 事件。

  • 常见子类
    • NioEventLoopGroup:基于 NIO 实现的事件循环组,适用于大多数场景。
    • EpollEventLoopGroup:基于 Linux 的高性能 Epoll 实现,仅支持 Linux 平台。
    • DefaultEventLoopGroup:非 I/O 任务的默认事件循环组。
  • 用途
    • 服务端需要两个 EventLoopGroup:
      • bossGroup:负责监听客户端的连接请求。
      • workerGroup:负责处理客户端的数据读写请求。
  • 示例
EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 只需 1 个线程接受连接
EventLoopGroup workerGroup = new NioEventLoopGroup();  // 处理读写操作的线程数为 CPU 核心数 * 2

2. Channel(通道)

Channel 是 Netty 对网络连接的抽象,表示网络通信的数据通道。它用于读写数据,并且 Netty 对 Channel 的读写是异步的。

  • 常见子类
    • NioSocketChannel:表示客户端的连接通道,基于 NIO 实现。
    • NioServerSocketChannel:表示服务端监听通道,接收客户端的连接。
  • 常用方法
    • writeAndFlush(Object msg):向 Channel 写入数据并立即刷新。
    • close():关闭 Channel,断开连接。
  • 示例
Channel channel = ctx.channel();  // 获取当前通道
channel.writeAndFlush("Hello Netty!");  // 向客户端发送消息

3. Pipeline(管道)

Pipeline 是 Netty 责任链模式的实现,用于管理一系列的处理器(Handler),处理数据的输入和输出。Netty 通过 Pipeline 实现对网络事件的处理流程控制。

  • 每个 Channel 都有一个 ChannelPipeline,其中可以添加多个 ChannelHandler 进行数据处理。
  • Pipeline 是双向链表,支持前向和后向传递事件。
  • 常用方法
    • addLast(ChannelHandler handler):将 Handler 添加到管道末尾。
    • addFirst(ChannelHandler handler):将 Handler 添加到管道最前面。
  • 示例
ch.pipeline().addLast(new StringDecoder());  // 添加解码器
ch.pipeline().addLast(new NettyServerHandler());  // 添加自定义业务处理器

说明

  • 入站处理器:处理客户端发来的请求,如 StringDecoder 解码器。
  • 出站处理器:处理返回给客户端的数据,如 StringEncoder 编码器。

4. ChannelHandler(事件处理器)

ChannelHandler 是 Netty 中事件处理器的接口,用于对网络事件(如数据读写、异常)进行处理。ChannelHandler 分为两种类型:

  • ChannelInboundHandler:入站事件处理器,处理数据读取事件。
  • ChannelOutboundHandler:出站事件处理器,处理数据写出事件。
  • 常用方法
    • channelRead(ChannelHandlerContext ctx, Object msg):读取客户端发送的数据。
    • write(ChannelHandlerContext ctx, Object msg):向客户端写出数据。
    • exceptionCaught(ChannelHandlerContext ctx, Throwable cause):处理异常。
  • 示例
public class NettyServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("收到消息:" + msg);ctx.writeAndFlush("服务器已接收:" + msg);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();  // 打印异常堆栈ctx.close();  // 关闭连接}
}

5.2 常用方法

1. bind()
  • 作用:绑定端口,用于启动服务端监听指定端口。
  • 调用示例
bootstrap.bind(8080).sync();  // 绑定端口 8080,并同步等待启动
  • 说明bind() 返回 ChannelFuture,通过 sync() 方法阻塞等待端口绑定完成。

2. connect()
  • 作用:连接到指定地址和端口,用于客户端连接到服务器。
  • 调用示例
bootstrap.connect("localhost", 8080).sync();  // 连接到本地 8080 端口的服务器
  • 说明connect() 方法也返回 ChannelFuture,可以通过回调函数监控连接状态。

3. addLast()
  • 作用:将 ChannelHandler 添加到 Pipeline 的末尾。
  • 调用示例
pipeline.addLast(new StringDecoder());  // 添加字符串解码器
pipeline.addLast(new NettyServerHandler());  // 添加自定义业务处理器
  • 说明addLast() 通常用于添加输入、输出数据的编解码器以及自定义的业务逻辑处理器。

4. addFirst()
  • 作用:将 ChannelHandler 添加到 Pipeline 的最前面。
  • 调用示例
pipeline.addFirst(new LoggingHandler(LogLevel.INFO));  // 在数据流入时先打印日志
  • 使用场景:如需在数据流入时进行日志记录、身份校验等操作,可以优先添加处理器。

5. writeAndFlush()
  • 作用:将数据写入 Channel 并立即刷新,确保数据被发送出去。
  • 调用示例
ctx.writeAndFlush("Hello, Netty!");
  • 说明:Netty 的 I/O 操作是异步的,因此调用 writeAndFlush() 需要手动刷新数据以立即发送。

5.3 示例代码说明

以下示例演示了 Pipeline 如何通过责任链模式调用 ChannelHandler

ch.pipeline().addLast(new StringDecoder());  // 解码入站数据
ch.pipeline().addLast(new StringEncoder());  // 编码出站数据
ch.pipeline().addLast(new NettyServerHandler());  // 添加业务处理器

在这里:

  1. 客户端发送字符串数据时,StringDecoder 将其解码为 String
  2. 服务器通过 NettyServerHandler 读取数据并处理逻辑。
  3. 服务器返回响应时,StringEncoderString 编码为字节流发送给客户端。

总结

通过 EventLoopGroup 线程池、Channel 通道、Pipeline 责任链和 Handler 事件处理器,Netty 构建了高效的异步 I/O 处理机制。熟练掌握这些核心组件及其方法,可以帮助我们轻松实现高并发、高性能的网络应用。


6. TCP 拆包与粘包问题

在基于 TCP 协议的网络编程中,数据传输是以字节流的形式进行的,发送方和接收方并不知道消息的边界。由于 TCP 是流式传输协议,没有消息的边界概念,因此会出现拆包粘包问题。以下是对拆包和粘包问题的详细说明,以及常见的解决方案。


6.1 拆包与粘包的概念

1. 粘包(Packet Stickiness)

粘包是指发送方发送的多条消息被接收方合并成一条消息,即接收方在一次读取操作中读取到了多条消息的数据。

  • 示例:

    • 发送方

      发送了两条消息:

      [Hello]
      [World]
      
    • 接收方

      在一次读取操作中读取到:

      [HelloWorld]
      
2. 拆包(Packet Fragmentation)

拆包是指发送方发送的一条完整消息被接收方分成多次接收,即接收方在一次读取操作中只读取到消息的一部分。

  • 示例:

    • 发送方

      发送了一条消息:

      [HelloWorld]
      
    • 接收方

      第一次读取到:

      [Hello]
      
    • 接收方

      第二次读取到:

      [World]
      

6.2 拆包与粘包的成因

  1. 网络传输协议特性:TCP 是流式传输协议,没有消息边界。
  2. 发送数据大小:数据量较大时,会被拆分成多个数据包传输。
  3. 接收缓存区大小:如果接收端缓存区较小,一次性无法接收完整数据,导致拆包。
  4. 操作系统协议栈优化:发送端会根据系统配置,将多条小数据合并成一个数据包,导致粘包。

6.3 拆包与粘包的影响

  • 数据丢失:应用程序会将不完整的数据视为异常,导致通信失败。
  • 数据错乱:数据包顺序错误或内容拼接错误,导致数据解析失败。
  • 程序崩溃:如果程序没有考虑拆包和粘包情况,可能会在解析时抛出异常或崩溃。

6.4 拆包与粘包问题的解决方案

Netty 提供了多种内置编解码器,帮助开发者轻松处理拆包和粘包问题:

  1. 定长消息方案 (FixedLengthFrameDecoder)
    固定长度读取数据,不足补齐。适用于固定格式的报文传输。
    示例:报文格式 [Hello ],长度固定为 10 字节。
  2. 分隔符方案 (DelimiterBasedFrameDecoder)
    通过自定义分隔符划分消息边界。适用于文本协议数据传输。
    示例:消息以 # 作为分隔符,如 Hello#Netty#
  3. 消息头部方案 (LengthFieldBasedFrameDecoder)
    消息前添加长度字段,指示消息体的长度。适用于变长二进制数据传输。
    示例[0005Hello],前 4 个字节为消息长度,表示正文长度为 5 字节

6.5 Netty 编解码器使用示例

以下是 Netty 编解码器的完整示例,包含消息格式说明、解析方式、以及适用场景,帮助理解每种编解码器的应用场景和实现方式。


1. FixedLengthFrameDecoder(定长消息解码器)

消息格式
[Hello     ]  // 固定 10 字节长度的消息,不足用空格补齐
解析方式
  • 每次读取固定长度的数据,如 10 字节。
  • 超过或不足的部分被切割或补齐。
适用场景
  • 金融系统报文传输:报文格式固定,长度一致。
  • 设备通信协议:如固定格式的传感器数据上传。

完整示例代码

服务端代码

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;public class FixedLengthServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new FixedLengthFrameDecoder(10));  // 每条消息长度固定为 10 字节ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new SimpleServerHandler());}});ChannelFuture future = bootstrap.bind(8080).sync();System.out.println("FixedLength Server 已启动,端口:8080");future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}static class SimpleServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("收到客户端消息:" + msg);ctx.writeAndFlush("服务端已收到:" + msg);}}
}

客户端代码

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;public class FixedLengthClient {public static void main(String[] args) throws InterruptedException {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) {ch.pipeline().addLast(new StringEncoder());  // 添加字符串编码器}});Channel channel = bootstrap.connect("localhost", 8080).sync().channel();channel.writeAndFlush("Hello     ");  // 长度补齐至 10 字节} finally {group.shutdownGracefully();}}
}

2. DelimiterBasedFrameDecoder(分隔符解码器)

消息格式
Hello Netty#
How are you?#
解析方式
  • 读取字节流,遇到 # 分隔符时,将其解析为一条完整的消息。
  • 截取分隔符前的数据作为消息内容。
适用场景
  • 文本协议:如聊天室、IM 系统。
  • 简单 RPC 通信协议:以特定字符区分消息边界。

完整示例代码

服务端代码

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;public class DelimiterServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.wrappedBuffer("#".getBytes())));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new SimpleServerHandler());}});ChannelFuture future = bootstrap.bind(8080).sync();System.out.println("Delimiter Server 已启动,端口:8080");future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}static class SimpleServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("收到客户端消息:" + msg);ctx.writeAndFlush("服务端已收到:" + msg + "#");}}
}

客户端代码

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;public class DelimiterClient {public static void main(String[] args) throws InterruptedException {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) {ch.pipeline().addLast(new SimpleClientHandler());}});Channel channel = bootstrap.connect("localhost", 8080).sync().channel();channel.writeAndFlush("Hello Netty#");channel.writeAndFlush("How are you?#");} finally {group.shutdownGracefully();}}static class SimpleClientHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("收到服务器消息:" + msg);}}
}

3. LengthFieldBasedFrameDecoder(基于消息头部的解码器)

消息格式
[0005Hello]  // 4 个字节的消息长度字段 + 消息正文
解析方式
  • 读取前 4 个字节,获取消息长度 0005(5 字节)。
  • 根据长度读取 Hello
适用场景
  • 二进制协议:如图片、视频等二进制数据传输。
  • 文件传输:用于传输大文件时,精确获取数据长度。

完整示例代码

服务端代码

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;public class LengthFieldServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));ch.pipeline().addLast(new LengthFieldPrepender(4));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new StringEncoder());ch.pipeline().addLast(new SimpleServerHandler());}});ChannelFuture future = bootstrap.bind(8080).sync();System.out.println("LengthField Server 已启动,端口:8080");future.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}static class SimpleServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println("收到客户端消息:" + msg);ctx.writeAndFlush("服务端已收到:" + msg);}}
}

客户端代码

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;public class LengthFieldClient {public static void main(String[] args) throws InterruptedException {EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) {ch.pipeline().addLast(new SimpleClientHandler());}});Channel channel = bootstrap.connect("localhost", 8080).sync().channel();channel.writeAndFlush("Hello LengthField Netty!");} finally {group.shutdownGracefully();}}
}

总结

  • FixedLengthFrameDecoder:适合定长数据,如金融报文。
  • DelimiterBasedFrameDecoder:适合文本协议,如聊天消息。
  • LengthFieldBasedFrameDecoder:适合变长二进制数据,如文件传输。

通过合理使用 Netty 提供的编解码器,可以有效解决 TCP 的拆包和粘包问题,提高系统的稳定性和数据传输的完整性。

6.6 解决方案的选择建议

  • 定长消息方案:适用于长度固定的消息场景,如金融报文系统。
  • 分隔符方案:适用于文本协议或字符串数据传输场景,如聊天室消息。
  • 消息头部方案:适用于二进制数据或变长消息传输场景,如文件传输系统。

7. Netty 的应用场景

Netty 作为高性能网络框架,适用于多种场景:

  • 高并发服务器:如 HTTP 服务器、RPC 框架。
  • 即时通讯系统:如聊天室、IM 服务。
  • 文件传输系统:支持大文件的高效上传和下载。
  • 物联网设备通信:处理海量设备数据的上传和控制。

8.总结与学习建议

Netty 是 Java 网络编程领域最流行的 NIO 框架,其简单易用高扩展性使其成为构建高并发、高吞吐量网络服务的首选。本文通过对 Netty 主要组件、常见问题及解决方案的详细讲解,帮助读者了解并掌握 Netty 的开发方法。

学习建议

  • 理解 Netty 的异步事件模型:熟悉 EventLoopGroupChannelPipeline 等核心组件的作用。
  • 掌握常用编解码器:根据场景选择合适的编解码器,如 StringDecoderLengthFieldBasedFrameDecoder
  • 动手实践:通过实现回显服务、聊天室、文件传输等小项目,加深对 Netty API 和多线程模型的理解。

通过不断学习和实践,可以从容应对大规模分布式系统的网络通信需求,构建高性能的网络应用程序。

相关文章:

构建高性能网络服务:从 Socket 原理到 Netty 应用实践

1. 引言 在 Java 网络编程中&#xff0c;Socket 是实现网络通信的基础&#xff08;可以查看我的上一篇博客&#xff09;。它封装了 TCP/IP 协议栈&#xff0c;提供了底层通信的核心能力。而 Netty 是在 Socket 和 NIO 的基础上&#xff0c;进一步封装的高性能、异步事件驱动的…...

Spring Boot教程之五十六:用 Apache Kafka 消费 JSON 消息

Spring Boot | 如何使用 Apache Kafka 消费 JSON 消息 Apache Kafka 是一个流处理系统&#xff0c;可让您在进程、应用程序和服务器之间发送消息。在本文中&#xff0c;我们将了解如何使用 Apache Kafka 在 Spring Boot 应用程序的控制台上发布 JSON 消息。 为了了解如何创建 …...

Elasticsearch ES|QL 地理空间索引加入纽约犯罪地图

可以根据地理空间数据连接两个索引。在本教程中&#xff0c;我将向你展示如何通过混合邻里多边形和 GPS 犯罪事件坐标来创建纽约市的犯罪地图。 安装 如果你还没有安装好自己的 Elasticsearch 及 Kibana 的话&#xff0c;请参考如下的链接来进行安装。 如何在 Linux&#xff0…...

csp-j知识点:联合(Union)的基本概念

一、联合&#xff08;Union&#xff09;的基本概念 联合是C/C语言中一种特殊的数据结构&#xff0c;它的主要特点是所有成员共享同一块内存空间。这意味着在任何给定时刻&#xff0c;联合中只有一个成员是有效的&#xff0c;因为它们都占用相同的物理内存位置。联合的大小取决…...

docker-compose 方式安装部署confluence

一、confluence简介 Confluence是一款由澳大利亚软件公司Atlassian开发的企业协作工具。它是一个基于web的团队协作平台&#xff0c;用于帮助团队成员共享和协同工作的知识、文档、想法和项目。 Confluence提供了一个集中管理和共享文档、知识库和项目信息的平台。团队成员可…...

深入理解计算机系统阅读笔记-第十二章

第12章 网络编程 12.1 客户端-服务器编程模型 每个网络应用都是基于客户端-服务器模型的。根据这个模型&#xff0c;一个应用时由一个服务器进程和一个或者多个客户端进程组成。服务器管理某种资源&#xff0c;并且通过操作这种资源来为它的客户端提供某种服务。例如&#xf…...

网络原理(九):数据链路层 - 以太网协议 应用层 - DNS 协议

目录 1. 数据链路层 1.1 以太网协议 1.1.1 以太网帧格式 1.2 mac 地址 1.2.1 IP 地址和 mac 地址的区别 1.3 帧中的类型字段 1.3.1 MTU - 最长载荷长度 1.3.2 ARP 协议 2. DNS 协议 1. 数据链路层 数据链路层, 是一个底层的层次, 主要用于交换机开发, 对于 Java 开发…...

rtthread学习笔记系列(4/5/6/7/15/16)

文章目录 4. 杂项4.1 检查是否否是2的幂 5. 预编译命令void类型和rt_noreturn类型的区别 6.map文件分析7.汇编.s文件7.1 汇编指令7.1.1 BX7.1.2 LR链接寄存器7.1.4 []的作用7.1.4 简单的指令 7.2 MSR7.3 PRIMASK寄存器7.4.中断启用禁用7.3 HardFault_Handler 15 ARM指针寄存器1…...

【拒绝算法PUA】3065. 超过阈值的最少操作数 I

系列文章目录 【拒绝算法PUA】0x00-位运算 【拒绝算法PUA】0x01- 区间比较技巧 【拒绝算法PUA】0x02- 区间合并技巧 【拒绝算法PUA】0x03 - LeetCode 排序类型刷题 【拒绝算法PUA】LeetCode每日一题系列刷题汇总-2025年持续刷新中 C刷题技巧总结&#xff1a; [温习C/C]0x04 刷…...

今日总结 2025-01-14

学习目标 掌握运用 VSCode 开发 uni - app 的配置流程。学会将配置完善的项目作为模板上传至 Git&#xff0c;实现复用。项目启动 创建项目&#xff1a;借助 Vue - Cli 方式创建项目&#xff0c;推荐从国内地址 https://gitee.com/dcloud/uni - preset - vue/repository/archiv…...

关于扫描模型 拓扑 和 传递贴图工作流笔记

关于MAYA拓扑和传递贴图的操作笔记 一、拓扑低模: 1、拓扑工作区位置: 1、准备出 目标 高模。 (高模的状态如上 ↑ )。 2、打开顶点吸附,和建模工具区,选择四边形绘制. 2、拓扑快捷键使…...

C#知识|泛型Generic概念与方法

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 关于泛型在前面学习记录过 《泛型集合List相关方法》、《Dictionary泛型集合的使用总结》&#xff1b; 其中泛型集合 List<T>、Dictionary<k,v>所在的命名空间为&#xff1a;System.Collection.Generic…...

centos 8 中安装Docker

注&#xff1a;本次样式安装使用的是centos8 操作系统。 1、镜像下载 具体的镜像下载地址各位可以去官网下载&#xff0c;选择适合你们的下载即可&#xff01; 1、CentOS官方下载地址&#xff1a;https://vault.centos.org/ 2、阿里云开源镜像站下载&#xff1a;centos安装包…...

vscode vue 自动格式化

vscode vue 自动格式化 安装Prettier和Vetur插件 选择设置&#xff0c;并且转到编辑文件。增加如下内容。 {"editor.formatOnSave": true,"editor.defaultFormatter": "esbenp.prettier-vscode","[vue]": {"editor.defaultFor…...

Webpack 5 混淆插件terser-webpack-plugin生命周期作用时机和使用注意事项

参考案例代码 海南酷森科技有限公司/webpack-simple-demo Terser&#xff08;简要的/简短的&#xff09; 混淆依据 混淆是发生在代码已经 bundle 之后的事情 变量或者函数在被引用或赋值时才能被混淆 孤立的函数或者变量可能会被移除&#xff0c;但不会被混淆&#xff0c;要…...

MQTT(Message Queuing Telemetry Transport)协议

文章目录 一、MQTT 的原理1. 通信模型2. 核心概念3. 工作流程 二、MQTT 的优势1. 轻量级2. 异步通信3. 可靠性4. 实时性5. 支持断线重连6. 跨平台支持7. 安全性 三、MQTT 的典型应用场景四、与其他协议的对比 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;…...

【MySQL学习笔记】MySQL存储过程

存储过程 1、基础语法2、变量2.1 系统变量2.2 用户自定义变量2.3 局部变量 3、if 流程控制4、参数5、case 流程控制6、循环结构6.1 while 循环6.2 repeat 循环6.3 loop 循环 7、游标 存储过程是事先经过编译并存储在数据库中的一段 SQL 语句的集合&#xff0c;调用存储过程可以…...

Vue2+OpenLayers实现折线绘制、起始点标记和轨迹打点的完整功能(提供Gitee源码)

目录 一、案例截图 二、安装OpenLayers库 三、代码实现 3.1、HTML页面 3.2、初始化变量 3.3、创建起始点位 3.4、遍历轨迹点 3.5、画折线 3.6、初始化弹窗信息 3.7、初始化地图上标点的点击事件 3.8、完整代码 四、Gitee源码 一、案例截图 二、安装OpenLayers库 n…...

基于Spring Boot的城市垃圾分类管理系统设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…...

linux: 文本编辑器vim

文本编辑器 vi的工作模式 (vim和vi一致) 进入vim的方法 方法一:输入 vim 文件名 此时左下角有 "文件名" 文件行数,字符数量 方法一: 输入 vim 新文件名 此时新建了一个文件并进入vim,左下角有 "文件名"[New File] 灰色的长方形就是光标,输入文字,左下…...

第19节 Node.js Express 框架

Express 是一个为Node.js设计的web开发框架&#xff0c;它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用&#xff0c;和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

Linux链表操作全解析

Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表&#xff1f;1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

Unity3D中Gfx.WaitForPresent优化方案

前言 在Unity中&#xff0c;Gfx.WaitForPresent占用CPU过高通常表示主线程在等待GPU完成渲染&#xff08;即CPU被阻塞&#xff09;&#xff0c;这表明存在GPU瓶颈或垂直同步/帧率设置问题。以下是系统的优化方案&#xff1a; 对惹&#xff0c;这里有一个游戏开发交流小组&…...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:

一、属性动画概述NETX 作用&#xff1a;实现组件通用属性的渐变过渡效果&#xff0c;提升用户体验。支持属性&#xff1a;width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项&#xff1a; 布局类属性&#xff08;如宽高&#xff09;变化时&#…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?

在建筑行业&#xff0c;项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升&#xff0c;传统的管理模式已经难以满足现代工程的需求。过去&#xff0c;许多企业依赖手工记录、口头沟通和分散的信息管理&#xff0c;导致效率低下、成本失控、风险频发。例如&#…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

Psychopy音频的使用

Psychopy音频的使用 本文主要解决以下问题&#xff1a; 指定音频引擎与设备&#xff1b;播放音频文件 本文所使用的环境&#xff1a; Python3.10 numpy2.2.6 psychopy2025.1.1 psychtoolbox3.0.19.14 一、音频配置 Psychopy文档链接为Sound - for audio playback — Psy…...