Spring Boot 中使用 Netty

2025/4/15

向 

一、什么是Netty

Netty 是 Java 中一个非常高性能的网络通信框架,用来开发服务器和客户端程序,主要用于处理 TCP/UDP 的网络连接,比如:

  • 聊天服务

  • 实时推送

  • 高并发网络通信(比如游戏、IoT、金融系统)

你可以把 Netty 理解为一种比 Java 原生 Socket 更方便、性能更强的“网络搭建工具”。再详细了解Netty的工作原理之前,我们先来看一下Java中最简单的客户端和服务器之间的连接。

二、最简单的 Java 网络通信

2.1什么是“客户端”和“服务端”?

我们先理解一个现实生活的比喻奶茶店点单系统

  • 服务端(Netty 服务):奶茶店(固定位置,等待别人来点单)

  • 客户端(浏览器、手机 App、Netty 客户端):顾客(谁想喝奶茶谁来)

  • 通信方式(TCP):电话(通过电话点单)

还可以更加省略一点来说就是 💬 一个人发送消息(客户端) ➜ 另一个人接收并回复(服务端)

2.2服务端

import java.io.*;
import java.net.*;public class Server {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(8080); // 在8080端口等别人来找System.out.println("服务端启动,等待客户端连接...");Socket socket = serverSocket.accept(); // 有人来连接,就接收它System.out.println("客户端连接进来了");// 输入输出流:用来读写数据BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 读PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // 写String line;while ((line = in.readLine()) != null) {System.out.println("收到客户端消息:" + line);out.println("我收到了:" + line); // 回给客户端}socket.close(); // 关闭连接serverSocket.close();}
}

2.3客户端

import java.io.*;
import java.net.*;public class Client {public static void main(String[] args) throws Exception {Socket socket = new Socket("127.0.0.1", 8080); // 连接本机服务端System.out.println("连接服务端成功!");// 输入输出BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in)); // 你键盘输入PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // 发消息BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 收消息String msg;while ((msg = userInput.readLine()) != null) {out.println(msg); // 发给服务端String reply = in.readLine(); // 读取服务端返回System.out.println("服务端说:" + reply);}socket.close();}
}

2.4 服务端和客户端之间的通信

首先是服务端先启动,会有如下显示,同时告诉顾客我家的店的端口号是8080。

服务端启动,等待客户端连接...

然后有顾客想买东西,通过 new Socket("127.0.0.1", 8080); // 连接本机服务端,即走进服务器店的大门8080。而在服务器这端,通过serverSocket.accept(); 看见有人来连接,就接收它,服务它。这时候客户端会输出如下

连接服务端成功!

服务端会输出如下:

客户端连接进来了

在客户端通过控制台输入:hello后,即通过如下代码接收到了你的输入,并存放在userInput变量中。

BufferedReader userInput = new BufferedReader(new InputStreamReader(System.in));

客户端通过out对象发消息 

 PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // 发消息

 客户端通过in对象接受消息 

BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 收消息

当 msg = userInput.readLine()) != null ,即当检测到客户端要发送消息就执行如下代码:

out.println(msg); // 发给服务端
String reply = in.readLine(); // 读取服务端返回
System.out.println("服务端说:" + reply);

out.println(msg)后,就将信息发送到了服务器端,服务器端就会输出如下

收到客户端消息:hello

同时在服务器端通过 out.println("我收到了:" + line); 回给客户端,客户端通过reply接收到消息,客户端就会输出

服务端说:我收到了:hello

2.5 客户端和服务器端的关系如下: 

角色作用
Server永远在等别人来(监听端口)
Client主动发起连接
Input/Output收发消息用的“通道”

 二、为什么需要线程模型?(Thread Model)

理解了基础的服务端和客户端通信,我们可以继续深入,了解一些稍微复杂一点的概念,即线程

在前面那个简单的服务端/客户端例子中,服务端是“串行”的,意思是:

  • 它在等待一个客户端连接。

  • 收到消息后再回复,接着等待下一个连接。

但是如果你有很多客户端同时发消息,服务端就会变得很慢,因为它只能一个一个地处理请求。

所以,我们需要更高效的处理方式:并发编程并发编程意味着能够同时处理多个任务,不等一个任务完成再开始下一个。而且每个任务都不会相互阻塞。这就是 线程池事件循环模型 的价值所在。在 Netty 中:

  • 线程池:多个线程可以同时处理多个连接。

  • 事件循环模型:每个线程(事件循环)只负责自己的任务,它会不停地轮询事件,比如客户端连接、数据读取等。

三、什么是“阻塞”和“非阻塞”?

❌ 阻塞:你去餐厅吃饭,服务员给你一个菜单,但你必须等着他们准备好菜才能吃,期间你不能干别的事。

✅ 非阻塞:你点菜后,服务员会告诉你“稍等一会儿”,然后你可以做其他事。只要菜做好了,服务员会告诉你,打断你做其他事,给你菜。

TCP 通信中的阻塞和非阻塞:

  • 阻塞:当你发起连接或请求时,程序会一直等待,直到连接建立或数据返回。

  • 非阻塞:发起请求后,程序不再等待,会继续执行其他任务。如果有返回结果,程序会处理返回。

Netty 默认就是 非阻塞 的,这样它能同时处理很多连接,不会被一个请求堵住。

四、Netty 是如何处理高并发的?

Netty通过使用一个线程模型 EventLoop(事件循环)来处理高并发。EventLoopGroup:管理多个线程(可以理解为多个服务员),负责处理网络事件。EventLoop:每个线程负责自己的一部分任务,比如处理某一个客户端的请求。

举例来看就是:

  • 一个服务端线程,负责监听连接(等待“顾客”进店)。
  • 多个工作线程,负责实际的通信(帮“顾客”点单、做菜)。

4.1 EventLoop 和 NIO 的关系

Netty 使用了 NIO(非阻塞 IO) 模型。NIO 让一个线程能处理多个连接。具体来说:

  • 使用 Selector 轮询(检查)每个连接的状态,看是否有数据到达。

  • 使用 Channel 来表示网络连接。

  • 使用 Buffer 来读取和写入数据。

这个模型让 Netty 在面对数千个并发连接时,也能保持高效。

总结来看,Netty的EventLoopGroup管理多个线程,每个线程只干特定的事情,假设某个线程只干连接客户端这个事情,又由于Netty引入了NIO模型,所以又让这个负责处理连接的线程具备了同时处理多个连接请求的能力

五、实际的 Netty 服务端示例

public class EchoServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup(1);  // 负责接收连接EventLoopGroup workerGroup = new NioEventLoopGroup(); // 负责处理请求try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new EchoServerHandler());ChannelFuture f = b.bind(8080).sync();  // 绑定端口,开始监听f.channel().closeFuture().sync();} finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}// 处理客户端发来的消息public static class EchoServerHandler 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(); // 出现异常关闭连接}}
}
  • ServerBootstrap:是 Netty 中用于启动服务端的核心类,启动 Netty 服务端。
  • bossGroup 和 workerGroup:管理事件循环,分别处理接收连接和处理数据的任务。
  • EchoServerHandler:是我们自定义的业务处理逻辑,收到客户端的消息就原封不动地回传。

六、实际使用的Netty

6.1 NettyServer类

ServerBootstrap:Netty服务器启动的核心类。

ServerBootstrap serverBootstrap = new ServerBootstrap().group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ServerChannelInitializer(delimiter, maxFrameLength)).localAddress(socketAddress).option(ChannelOption.SO_BACKLOG, 1024).childOption(ChannelOption.SO_KEEPALIVE, true);
  • .group(bossGroup, workGroup) 配置监听线程和工作线程。
  • .channel(NioServerSocketChannel.class): 这里指定了服务端的 Channel 类型。NioServerSocketChannel 适用于 NIO(非阻塞 IO),这是一种处理高并发的方式。
  • .childHandler(new ServerChannelInitializer(delimiter, maxFrameLength)): 为每个连接配置一个 ChannelInitializer,在每个连接初始化时(每个客户端连接时)会被调用。ServerChannelInitializer 是自定义的初始化类,配置如何处理数据的编解码、业务逻辑等。
  • .localAddress(socketAddress): 配置绑定的本地地址和端口
  • .option(ChannelOption.SO_BACKLOG, 1024): 配置服务器端的连接队列大小。队列最大长度设置为 1024。
  • .childOption(ChannelOption.SO_KEEPALIVE, true): 设置 TCP KeepAlive,确保连接在空闲时依然存活。

6.1.1启动并绑定端口

ChannelFuture channelFuture = serverBootstrap.bind(socketAddress).sync();
  • .bind(socketAddress): 绑定到指定的 socketAddress,开始监听客户端的连接。

  • .sync(): 阻塞方法,直到端口绑定成功并启动后,才会继续执行。ChannelFuture 用于获取当前操作的结果(是否成功绑定)

6.2 SeverChannelInitializer类

在NettyServer类中,我们是调用了SeverChannelInitializer类的,我们使用SeverChannelInitializer类来配置如何处理数据的编解码、业务逻辑等。当每个客户端连接进来时,配置它的 Channel 的“流水线”——也就是这个连接收到/发送数据时,按什么顺序怎么处理。可以把它理解为工厂生产线的“组装说明书”。

package com....nettyService;import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.logging.LoggingHandler;public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {private String DELIMITER;private int MAXFRAMELENGTH;public ServerChannelInitializer(String delimiter, int maxFrameLength) {DELIMITER = delimiter;MAXFRAMELENGTH = maxFrameLength;}@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {socketChannel.pipeline().addLast("logging", new LoggingHandler("DEBUG"));socketChannel.pipeline().addLast("decoder", new HL7Decoder());socketChannel.pipeline().addLast("encoder", new HL7Encoder());socketChannel.pipeline().addLast(new NettyServerHandler());}}

SeverChannelInitializer首先继承了ChannelInitializer<SocketChannel>,这样没有一个新的连接的时候Netty 就会调用 initChannel() 方法,给这个连接安装一套“处理器组合”(pipeline)。

而这一套“处理器组合”当接收到客户端发送的消息执行顺序如下:

【客户端】==> socketChannel
         ↓
[LoggingHandler](打印日志)
         ↓
[HL7Decoder](解码消息)
         ↓
[NettyServerHandler](业务处理)

当服务端要回复消息,其执行顺序如下:

   NettyServerHandler.write()
         ↓
       [HL7Encoder](编码为字节)
         ↓
         [LoggingHandler](打印)
         ↓
          【客户端】

6.3 NettySeverHandler类

在SeverChannelInitializer类中,其写好了业务处理顺序,在处理业务时,其处理业务的核心是NettySeverHandler类来实现的

package com.....nettyService;import com...component.commons.utils.BeanUtils;
import com...emergency.service.impl.BS2800MPacketParse;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;@Component
public class NettyServerHandler extends SimpleChannelInboundHandler<String> {private static final Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);private BS2800MPacketParse bs2800MPacketParse = BeanUtils.getBean(BS2800MPacketParse.class);/*** 装载所有客户端channel的组*/private static final Map<String, Channel> ipChannelMap = new HashMap<>();/*** 客户端连接过来会触发*/@Overridepublic void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {Channel channel = channelHandlerContext.channel();ipChannelMap.put(channel.remoteAddress().toString(), channel);logger.info("客户端连接:" + channelHandlerContext);}/*** 客户端发消息过来会触发*/@Overridepublic void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {Channel channel = channelHandlerContext.channel();logger.info("服务端接收到客户端消息");
//        logger.info("发送消息的客户端地址:" + channel.remoteAddress());logger.info("发送消息的客户端所发消息:" + msg);String result = msg;String msa = handleParams(channelHandlerContext, result);if (ObjectUtils.isNotEmpty(msa)) {channel.writeAndFlush(msa);}}@Overridepublic void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {super.channelReadComplete(channelHandlerContext);}@Overridepublic void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {Channel channel = channelHandlerContext.channel();// 当通道变为非活动状态(断开连接)时,将其从 ChannelGroup 中移除String ip = channel.remoteAddress().toString();if (ipChannelMap.containsKey(ip)) {ipChannelMap.remove(ip);if (!channel.isActive() || channel == null) {channelHandlerContext.close();}}logger.info("客户端地址为:" + ip + "的连接已断开");}/*** 发生异常触发*/@Overridepublic void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception {logger.warn(cause.toString());}/*** 处理接收报文消息*/public String handleParams(ChannelHandlerContext channelHandlerContext, String msg) {String msa = null;SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");Channel channel = channelHandlerContext.channel();if (channel.remoteAddress().toString().contains("10.10.51.213")) {if (ObjectUtils.isNotEmpty(msg)) {String result[] = msg.split("\r");if (ObjectUtils.isNotEmpty(result) && result.length > 0) {String msh = null;for (String string : result) {if (string.contains("MSH")) {msh = string;}}if (msh.contains("ORU^R01")) {Date date = new Date();String temp[] = msh.split("\\|", -1);if (ObjectUtils.isNotEmpty(temp) && temp.length > 9) {msa = "MSH|^~\\&|||||" + dateFormat.format(date) + "||ACK^R01|" + temp[9] + "|P|2.3.1||||0||ASCII|||";String str = "MSA|AA|" + temp[9] + "|Message accepted|||0|";msa = msa + "\r" + str;Map<String, String> paramMap = new HashMap<>();paramMap.put(temp[9], msg);bs2800MPacketParse.parse(msg);return msa;}}}}}return msa;}}

6.3.1继承

NettyServerHandler继承SimpleChannelInboundHandler<String> 每次接收到客户端消息(已经是 String 类型,说明解码器已完成解码),就会触发 channelRead0() 方法。我们可以在这里处理逻辑、保存数据、做回复等

6.3.2channelActive

有客户端连接进来时,Netty 会自动调用这个方法。将客户端的 Channel 保存到 ipChannelMap 中,方便后面用 IP 找到连接。同时打印客户端连接信息。

6.3.3channelRead0

每当客户端发一条消息过来,就会自动执行这里!先获取当前的 Channel(对应客户端)

Channel channel = channelHandlerContext.channel();

打印日志,方便调试看到收到的数据

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75979.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【QT】 QT定时器的使用

QT定时器的使用 1. QTimer介绍&#xff08;1&#xff09;QTimer的使用方法步骤示例代码1&#xff1a;定时器的启动和关闭现象&#xff1a;示例代码2&#xff1a;定时器每隔1s在标签上切换图片现象&#xff1a; (2)实际开发的作用 2.日期 QDate(1)主要方法 3.时间 QTime(1)主要方…

排序算法详细介绍对比及备考建议

文章目录 排序算法对比基本概要 算法逐一介绍1. 冒泡排序&#xff08;Bubble Sort&#xff09;2. 选择排序&#xff08;Selection Sort&#xff09;3. 插入排序&#xff08;Insertion Sort&#xff09;&#x1f31f;&#x1f31f;4. 希尔排序&#xff08;Shell Sort&#xff09…

Docker华为云创建私人镜像仓库

Docker华为云创建私人镜像仓库 在华为云官网的 产品 中搜索 容器镜像服务 &#xff1a; 或者在其他页面的搜索栏中搜索 容器镜像服务 &#xff1a; 进入到页面后&#xff0c;点击 创建组织 &#xff08;华为云的镜像仓库称为组织&#xff09;&#xff1a; 设置组织名字后&…

微信小程序-自定义toast

微信小程序-自定义toast 微信小程序原生的toast最多能显示两行文字。方案1:方案2 微信小程序原生的toast最多能显示两行文字。 有时候并不能满足业务需求。所以我们需要使用第三方或者自定义。 方案1: 第三方vant-toast 微信小程序下载引入第三方vant之后。 在需要使用的页面…

安卓手游逆向

一、环境安装 1.1、安装Java环境 1.2、安装SDK环境 1.3、安装NDK环境 二、APK 2.1、文件结构 2.2、打包流程 2.3、安装流程 应用安装涉及目录: system/app ----->系统自带的应用程序,获得adb root权限才能删除。 data/app ------->用户程序安装的目录,安装…

VSCode Continue 扩展踩坑记录

Trae 是一款很优秀的 AI 开发工具&#xff0c;但目前支持的平台还较少&#xff0c;比如不支持 Win7&#xff0c;不支持 Linux&#xff0c;为了在这些平台上进行开发&#xff0c;我需要寻找一个替代品。经过网上搜索&#xff0c;选择了 VSCode Continue 扩展&#xff0c;但在使…

Elasticsearch:AI 助理 - 从通才到专才

作者&#xff1a;来自 Elastic Thorben Jndling 在 AI 世界中&#xff0c;关于构建针对特定领域定制的大型语言模型&#xff08;large language models - LLM&#xff09;的话题备受关注 —— 不论是为了更好的安全性、上下文理解、专业能力&#xff0c;还是更高的准确率。这个…

【ARM】MDK烧录提示Error:failed to execute‘ ‘

1、 文档目标 解决在烧录程序的时候&#xff0c;因为选择了错误的烧录方式导致下载失败的情况。 2、 问题场景 在烧录程序的时候出现了提示&#xff1a;“Error&#xff1a;failed to execute ’ ”&#xff08;如图2-1&#xff09;。检测Target->Debug配置发现没有问题&a…

系统分析师(六)-- 计算机网络

概述 TCP/IP 协议族 DNS DHCP 网络规划与设计 逻辑网络设计 物理网络设计 题目 层次化网络设计 网络冗余设计 综合布线系统 IP地址 网络接入技术 其他网络技术应用 物联网

优化运营、降低成本、提高服务质量的智慧物流开源了

智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒&#xff0c;省去繁琐重复的适配流程&#xff0c;实现芯片、算法、应用的全流程组合&#xff0c;从而大大减少企业级应用约95%的开发成本可通过边缘计算技术…

从One-Hot到TF-IDF:NLP词向量演进解析与业务实战指南(一)

从One-Hot到TF-IDF&#xff1a;词向量演进之路 开场白&#xff1a; 想象一下&#xff0c;你试图用Excel表格分析《红楼梦》的情感倾向——每个字词都是孤立的单元格&#xff0c;计算机看到的只有冰冷的0和1&#xff0c;而“黛玉葬花”的凄美意境却消失得无影无踪。这就是NLP工…

2. kubernetes操作概览

以下是 Kubernetes 的核心操作概览&#xff0c;涵盖常用命令、资源管理和典型场景的操作流程&#xff1a; 1. 核心操作工具 (1) kubectl 命令行工具 Kubernetes 的所有操作均通过 kubectl 实现&#xff0c;常用命令如下&#xff1a; 操作类型命令示例作用说明查看资源状态ku…

从Ampere到Hopper:GPU架构演进对AI模型训练的颠覆性影响

一、GPU架构演进的底层逻辑 AI大模型训练效率的提升始终与GPU架构的迭代深度绑定。从Ampere到Hopper的演进路径中&#xff0c;英伟达通过‌张量核心升级‌、‌显存架构优化‌、‌计算范式革新‌三大技术路线&#xff0c;将LLM&#xff08;大语言模型&#xff09;训练效率提升至…

p2p的发展

PCDN&#xff08;P2P内容分发网络&#xff09;行业目前处于快速发展阶段&#xff0c;面临机遇与挑战并存的局面。 一、发展机遇 技术融合推动 边缘计算与5G普及&#xff1a;5G的高带宽、低延迟特性与边缘计算技术结合&#xff0c;显著提升PCDN性能&#xff0c;降低延迟&#x…

计算机视觉与深度学习 | 视觉里程计(Visual Odometry, VO)学习思路总结

视觉里程计(Visual Odometry, VO)学习思路总结 视觉里程计(VO)是通过摄像头捕获的图像序列估计相机运动轨迹的技术,广泛应用于机器人、自动驾驶和增强现实等领域。以下是一个系统的学习路径,涵盖基础理论、核心算法、工具及实践建议:一、基础理论与数学准备 核心数学工具…

Ubuntu 24.04 中文输入法安装

搜狗输入法&#xff0c;在Ubuntu 24.04上使用失败&#xff0c;安装教程如下 https://shurufa.sogou.com/linux/guide 出现问题的情况&#xff0c;是这个帖子里描述的&#xff1a; https://forum.ubuntu.org.cn/viewtopic.php?t493893 后面通过google拼音输入法解决了&#x…

阿里云 MSE Nacos 发布全新“安全防护”模块,简化安全配置,提升数据保护

作者&#xff1a;张文浩 阿里云在其微服务引擎&#xff08;MSE&#xff09;注册配置中心 Nacos 上正式推出全新“安全防护”功能模块&#xff0c;旨在帮助企业用户有效管理安全状态和降低开启安全相关功能的学习成本&#xff0c;提升微服务架构的安全性。首期推出的“安全防护…

C#核心(23)StringBuilder

前言 我们先前已经了解了String的一些基本规则和常见的用法,今天就来讲一下和string有所区别的StringBulider。 在 C# 中,StringBuilder 类是一个非常有用的工具,特别是在需要频繁修改字符串时。与 String 类型不同,StringBuilder 类提供了一种动态字符串,可以在不创建新…

活动图与流程图的区别与联系:深入理解两种建模工具

目录 前言1. 活动图概述1.1 活动图的定义1.2 活动图的基本构成要素1.3 活动图的应用场景 2. 流程图概述2.1 流程图的定义2.2 流程图的基本构成要素2.3 流程图的应用场景 3. 活动图与流程图的联系4. 活动图与流程图的区别4.1 所属体系不同4.2 表达能力差异4.3 使用目的与语境4.4…

idea运行springboot项目,运行时不能生成target

1&#xff0c;问题 项目本来运行正常&#xff0c;突然重启项目运行时&#xff0c;提醒主类找不到&#xff0c;发现target未生成 2&#xff0c;解决办法 查看.idea里面的文件&#xff0c;正常是下面这样的 如果有缺失&#xff0c;删除.idea里面的文件&#xff0c;清除idea缓…