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

Netty Review - 探究Netty服务端主程序无异常退出的背后机制

文章目录

  • 概述
  • 故障场景
  • 尝试改进
  • 问题分析
    • 铺垫: Daemon线程
    • Netty服务端启动源码分析
    • 逻辑分析
  • 如何避免Netty服务端意外退出
  • 最佳实践

在这里插入图片描述


概述

在使用Netty进行服务端程序开发时,初学者可能会遇到各种问题,其中之一就是服务端意外退出的问题。这种问题可能会出现在程序启动后,没有发生任何异常的情况下,突然退出。导致这种情况发生的原因可能是代码中存在一些隐含的问题 。

接下来我们通过一个案例来演示一下这个问题

故障场景

package com.artisan.nettycase.a01exist;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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;/*** @author 小工匠* @version 1.0* @mark: show me the code , change the world*/
public class ServerAbnormalExitExample {public static void main(String[] args) throws InterruptedException {// 创建两个事件循环组,bossGroup 用于接收客户端连接,workerGroup 用于处理客户端连接的读写事件EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 用一个线程处理接收连接的事件EventLoopGroup workerGroup = new NioEventLoopGroup(4); // 用四个线程处理处理客户端连接的读写事件try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // 设置服务端 Channel 的类型为 NIO,这里使用 NioServerSocketChannel.option(ChannelOption.SO_BACKLOG, 1024) // 设置一些 TCP 的参数,这里设置了连接缓冲区大小.handler(new LoggingHandler(LogLevel.INFO)) // 添加一个日志处理器,用于打印一些调试日志.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();pipeline.addLast(new LoggingHandler(LogLevel.INFO)); // 添加一个日志处理器,用于打印客户端的请求日志}});// 同步的方式绑定服务端监听端口ChannelFuture future = serverBootstrap.bind(9000).sync(); // 绑定端口并启动服务端// 等待服务端监听端口关闭future.channel().closeFuture().sync();} finally {// 优雅地关闭事件循环组bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}

运行程序,结果如下:

在这里插入图片描述

尝试改进

发现没有监听CloseFuture,于是对代码进行修改,

// 同步的方式绑定服务端监听端口ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();channelFuture.channel().closeFuture().addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {// 模拟业务代码System.out.println(Thread.currentThread().getName() + " --- " + channelFuture.channel().toString() + "链路关闭");}});

还会发生服务器套接字直接关闭、进程退出的问题 。

在这里插入图片描述


问题分析

铺垫: Daemon线程

Java中的"Daemon"线程(守护线程)是一种特殊类型的线程,其特点是当所有的非守护线程都结束时,它会自动退出。相对于普通线程(非守护线程),守护线程更像是一种服务提供者,它们在后台默默地执行一些任务,而不会阻止JVM的正常关闭。

守护线程的特点如下:

  1. 在创建线程时指定为守护线程: 可以通过Thread类的setDaemon(boolean on)方法将线程设置为守护线程,其中on参数为true表示将线程设置为守护线程,为false表示设置为普通线程。

  2. 守护线程的生命周期受主线程的影响: 当所有的非守护线程结束时,守护线程会自动退出。这意味着,如果所有的非守护线程都结束了,即使守护线程还有未完成的任务,JVM也会立即退出。

  3. 通常用于执行后台任务: 由于守护线程的特性,通常用于执行一些后台任务,比如垃圾回收器、JVM监控等。

  4. 不能持有关键资源: 由于守护线程会在JVM退出时自动终止,因此不适合持有关键资源,比如文件或者数据库连接等。因为它们可能会在守护线程尚未执行完毕时被关闭,从而导致程序出现异常。

  5. 守护线程与非守护线程的区别: 主要区别在于JVM的退出条件,非守护线程结束时不会影响JVM的退出,而守护线程结束时可能会导致JVM立即退出。

来看个代码:

public class DaemonThreadExample {public static void main(String[] args) {Thread daemonThread = new Thread(() -> {while (true) {System.out.println("Daemon Thread is running...");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 将线程设置为守护线程daemonThread.setDaemon(true);// 启动守护线程daemonThread.start();// 主线程休眠一段时间try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Main thread is exiting...");}
}

在这里插入图片描述

我们可以知道: 守护线程是在所有非守护线程结束时自动退出的。因此,如果主线程退出,而守护线程是唯一剩下的线程,那么守护线程也会立即退出。所以,即使是守护线程,当所有非守护线程都退出时,它也会终止

故结论如下:

  1. 在Java虚拟机中,即使主线程(通常是main线程)结束,只要还有活跃的非守护线程(用户线程)在运行,虚拟机进程仍然会保持活跃状态。只有当所有的非守护线程都结束时,虚拟机的进程才会结束。

  2. 当主线程(main线程)结束时,如果此时运行的其他线程全部是守护线程(Daemon线程),那么虚拟机会停止这些守护线程并退出。但是,如果此时正在运行的其他线程中有非守护线程,那么虚拟机将等待所有的非守护线程结束后才会退出。这意味着虚拟机会等待所有的非守护线程退出,不会因为主线程结束而立即退出。


Netty服务端启动源码分析

Netty Review - 服务端channel注册流程源码解析

在这里插入图片描述

通过分析源码我们可以知道: 在Netty中,当调用bootstrap.bind(port).sync().channel()方法时,确实不是在调用方的线程(比如main线程)中执行,而是通过Netty的NioEventLoop线程执行。这是因为Netty采用了异步的事件驱动模型,在调用bind方法时,实际上是注册了一个事件监听器,在后续端口绑定完成时会通过NioEventLoop线程执行相应的逻辑。


最终的执行结果其实就是调用了Java NIOSocket的端口绑定操作:

javaChannel().socket().bind(localAddress, config.getBacklog());

在Netty中,NioEventLoop是一个事件循环,负责处理网络事件,包括接受连接、读写数据等。每个NioEventLoop都绑定了一个线程,它会不断地从事件队列中取出事件,并处理这些事件。因此,当调用bootstrap.bind(port).sync().channel()方法时,实际上是将端口绑定操作放入了NioEventLoop的事件队列中,由NioEventLoop线程来执行。这样做的好处是可以避免阻塞调用方的线程,提高了程序的并发性能。


逻辑分析

我们知道: 端口绑定操作执行完成之后,main函数就不会阻塞,如果后续没有同步代码,main线程就会退出。

那我们思考一个问题: main线程退出是否意味着JVM进程一定退出吗?

并非如此,只有所有非守护线程全部执行完成,进程才会退出。

我们通过打印线程名称来看一下

System.out.println(Thread.currentThread().getName() + " --- " + channelFuture.channel().toString() + "链路关闭");

在这里插入图片描述
当然了,也可以通过Jconsole、jvisualvm、jmc等工具来观察 。


通过对 NioEventLoop源码进行分析,可以明确如下几点。

  • NioEventLoop是非守护线程
  • NioEventLoop运行之后,不会主动退出
  • 只有调用shutdown系列方法,NioEventLoop才会退出

我们写的程序在调用Netty的shutdownGracefully()方法后,导致NioEventLoop线程退出,从而整个系统的非守护线程都执行完成,而主线程也早已执行完毕,因此JVM进程退出。

主要的原因有两点:

  1. 端口绑定操作执行非常快:尽管调用bootstrap.bind(PORT).sync()会同步阻塞主线程,等待端口绑定的结果,但是由于端口绑定操作执行非常快速,一旦完成,程序就会继续向下执行。

  2. 调用shutdownGracefully()方法:在finally块中调用了bossGroup.shutdownGracefully()workerGroup.shutdownGracefully(),这两个方法会关闭服务端的TCP连接接入线程池和处理客户端网络I/O读写的工作线程池。当这两个线程池都关闭后,NioEventLoop线程也会退出,整个系统的非守护线程执行完成。因为主线程也早已执行完毕,所以JVM进程会退出。


当我们尝试

channelFuture.channel().closeFuture().addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {// 模拟业务代码System.out.println(Thread.currentThread().getName() + " --- " + channelFuture.channel().toString() + "链路关闭");}});

也依然无法阻值JVM退出,虽然增加了服务端连接关闭的监听事件之后,不会阻塞mainO)线程的执行,端口绑定成功之后,main线程继续向下执行,由于在finally中增加了线程池关闭代码,NioEventoop 线程主动退出,系统中没有正在运行的非守护线程了,所以JVM 进程退出。


Netty是一个异步非阻塞的通信框架,所有的IO操作都是异步的,但是为了方便使用,例如在有些场景下应用需要同步阻塞等待一些I/O操作的结果,所以提供了ChannelFuture,它主要提供以下两种能力。

  • 通过注册监听器GenericFutureListener,可以异步等待 I/O执行结果
  • 通过sync或者await,主动阻塞当前调用方的线程,等待操作结果,也就是通常
    说的异步转同步。

针对这个问题,重点在于理解Netty的异步非阻塞通信机制和ChannelFuture机制。Netty提供了ChannelFuture机制,通过注册监听器或者阻塞等待操作结果,可以实现异步转同步的操作。

因此,在使用Netty时,需要合理地处理异步操作,以充分利用Netty的优势,并避免出现意外退出的情况。


如何避免Netty服务端意外退出

通过对Netty服务端意外退出问题的分析,我们可以采取不同的修改策略来防止这种情况的发生。

  1. 监听NioServerSocketChannel的关闭事件并同步阻塞main函数:
// 监听NioServerSocketChannel的关闭事件并同步阻塞main函数
channelFuture.channel().closeFuture().sync();

这种方法会在NioServerSocketChannel关闭时阻塞主线程,直到关闭事件发生。这样可以保证主线程在服务端关闭之前不会退出,从而确保服务端的正常运行。

启动服务后,再次观察线程dump

在这里插入图片描述

搞个线程DUMP看一下

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 在链路关闭时再释放线程池和连接句柄:
channelFuture.channel().closeFuture().addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {// 模拟业务代码System.out.println(Thread.currentThread().getName() + " --- " + channelFuture.channel().toString() + "链路关闭");boss.shutdownGracefully();worker.shutdownGracefully();}
});

这种方法会在链路关闭时异步执行释放线程池和连接句柄的操作。通过添加监听器,可以在关闭事件发生时执行相应的操作,从而避免在主线程中主动调用shutdownGracefully()方法导致的意外退出问题。


最佳实践

在实际项目中这些错误可能会导致服务端意外退出或者线程阻塞等问题。 建议如下

错误用法:这种用法会导致调用方的线程一直被阻塞,直到服务端监听句柄关闭。

  1. 初始化 Netty 服务端。
  2. 同步阻塞等待服务端端口关闭
  3. 释放 I/0 线程资源和句柄等
  4. 调用方线程被释放。

正确用法:服务端启动之后注册监听器监听服务端句柄关闭事件,待服务端关闭之后
异步调用 shutdownGracefull释放资源,这样调用方线程就可以快速返回,不会被阻塞。

  1. 初始化 Netty 服务端。
  2. 绑定监听端口。
  3. 向CloseFuture注册监听器,在监听器中释放资源
  4. 调用方线程返回。

推荐通过调用EventLoopGroupshutdownGracefully方法来优雅地关闭服务端,以完成内存队列中积压消息的处理、链路的关闭和EventLoop线程的退出。这样可以实现停机不中断业务。 (单靠Netty框架可能无法完全保证服务的可靠性,需要应用程序的其他配合来实现。)

总的来说,正确理解和使用Netty的异步特性是非常重要的。合理地利用Netty的异步非阻塞模型可以提高系统的性能和并发能力,同时避免出现意外退出和性能问题。

在这里插入图片描述

相关文章:

Netty Review - 探究Netty服务端主程序无异常退出的背后机制

文章目录 概述故障场景尝试改进问题分析铺垫&#xff1a; Daemon线程Netty服务端启动源码分析逻辑分析 如何避免Netty服务端意外退出最佳实践 概述 在使用Netty进行服务端程序开发时&#xff0c;初学者可能会遇到各种问题&#xff0c;其中之一就是服务端意外退出的问题。这种问…...

【兔子机器人】修改GO电机id(软件方法、硬件方法)

一、硬件方法 利用上位机直接修改GO电机的id号&#xff1a; 打开调试助手&#xff0c;点击“调试”&#xff0c;查询电机&#xff0c;修改id号&#xff0c;即可。 但先将四个GO电机连接线拔掉&#xff0c;不然会将连接的电机一并修改。 利用24V电源给GO电机供电。 二、软件方…...

Spring MVC | Spring MVC 的“核心类” 和 “注解”

目录: Spring MVC 的“核心类” 和 “注解” &#xff1a;1.DispatcherServlet (前端控制器)2.Controller 注解3.RequestMapping 注解3.1 RequestMapping 注解的 “使用”标注在 “方法” 上标注在 “类” 上 3.2 RequestMapping 注解的 “属性” 4.组合注解4.1 请求处理方法的…...

Python 创建PPT

本篇为如何使用Python来创建ppt文件。 创建PPT 安装必要的库 命令如下&#xff1a; pip install python-pptx 安装过程&#xff1a; 创建ppt文件 在当前目录下创建一个test的ppt文件。其中包含两页&#xff0c;分别使用了不同的布局。 第一页设置了标题和内容。第二页只设…...

【工具】Git的24种常用命令

相关链接 传送门&#xff1a;>>>【工具】Git的介绍与安装<< 1.Git配置邮箱和用户 第一次使用Git软件&#xff0c;需要告诉Git软件你的名称和邮箱&#xff0c;否则无法将文件纳入到版本库中进行版本管理。 原因&#xff1a;多人协作时&#xff0c;不同的用户可…...

rabbitmq 基本总结

rabbitmq 的基本概念 vhost、broker、producer、 consumer、 exchange、 queue、 routing key rabbitmq 常用的队列类型&#xff0c;工作队列&#xff08;简单队列&#xff09;,pub/sub, routing key&#xff0c; topic 模式 <dependency><groupId>com.rabbitmq&l…...

7、Copmose自定义颜色和主题切换

Copmose自定义颜色和主题切换 一起颜色的设置的都是在res/values/colors里面去做颜色&#xff0c; 但是当使用compose的时候&#xff0c;抛弃了使用了ui.theme底下的Color.kt和Theme.kt 但是默认使用的是MaterialTheme主题&#xff0c;里面的颜色字段不能定义&#xff0c;因此…...

js-判断变量是否定义

if (typeof myVar undefined) {// myVar (未定义) 或 (已定义但未初始化) } else {// myVar (已定义和已初始化) } 参考 https://www.cnblogs.com/redFeather/p/17662966.html...

视频远程监控平台EasyCVR集成后播放只有一帧画面的原因排查与解决

智慧安防视频监控平台EasyCVR能在复杂的网络环境中&#xff08;专网、局域网、广域网、VPN、公网等&#xff09;将前端海量的设备进行统一集中接入与视频汇聚管理&#xff0c;平台可支持的接入协议包括&#xff1a;国标GB28181、RTSP/Onvif、RTMP&#xff0c;以及厂家的私有协议…...

Pulsar 社区周报 | No.2024.03.08 Pulsar-Spark Connector 助力实时计算

关于 Apache Pulsar Apache Pulsar 是 Apache 软件基金会顶级项目&#xff0c;是下一代云原生分布式消息流平台&#xff0c;集消息、存储、轻量化函数式计算为一体&#xff0c;采用计算与存储分离架构设计&#xff0c;支持多租户、持久化存储、多机房跨区域数据复制&#xff0c…...

Redis--线程模型详解

Redis线程模型 Redis内部使用的文件事件处理器&#xff08;基于Reactor模式开发的&#xff09;file event handler是单线程的&#xff0c;所以Redis线程模型才叫单线程模型&#xff0c;它采用IO多路复用机制同时监听多个socket&#xff0c;当被监听的socket准备好执行accep、r…...

[备赛笔记]——5G大唐杯(5G考试等级考考试基础试题)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;Vir2021GKBS &#x1f43c;本文由…...

【解读】OWASP 大语言模型(LLM)安全测评基准V1.0

大语言模型&#xff08;LLM&#xff0c;Large Language Model&#xff09;是指参数量巨大、能够处理海量数据的模型, 此类模型通常具有大规模的参数&#xff0c;使得它们能够处理更复杂的问题&#xff0c;并学习更广泛的知识。自2022 年以来&#xff0c;LLM技术在得到了广泛的应…...

java数据结构与算法刷题-----LeetCode77. 组合

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 递归实现 解题思路 这种题只能暴力求解&#xff0c;枚举所有可…...

网络安全运营的工作内容(附资料下载)

【推荐】最新网络安全运营方案和实践合集&#xff08;共80多份&#xff09;.zip 网络安全运营的工作内容是一个多层次、多维度的体系&#xff0c;涵盖了多个关键领域以确保网络环境的稳定和安全。以下是一些主要的工作内容&#xff1a; 安全策略制定与实施&#xff1a; 制定网…...

华为OD面试分享13(2024年)

华为OD面经 二战失败选手,双非一本部门目标院校,数学与应用数学专业,无相关工作经验也没有什么拿得出手的项目。3月中旬开始重新学java(大学里有学过一个学期的java,很水)。期间经常通宵肝,学习框架、刷leedcode,可能是因为数学专业出身,数据结构和算法这一块学起来并…...

Android14之解决报错:No module named sepolgen(一百九十二)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…...

数电学习笔记——逻辑函数的代数法化简

目录 逻辑函数的化简原则 与或逻辑的化简 1、吸收律(1) ( ABABA) 2、吸收律(2)(3)( AABA&#xff1b;AABAB) 3、多余项定律( ABACBCABAC) 4、拆项法 5、添项法 逻辑函数的化简原则 (1)逻辑函数所用的门最少 (2)各个门的输入端要少 (3)逻辑电路所用的级数要少 (4)逻辑…...

react实战——react旅游网

慕课网react实战 搭建项目问题1.按照官网在index.tsx中引入antd出错&#xff1f;2.typescript中如何使用react-router3.react-router3.1 V63.2 V53.3V6实现私有路由 4.函数式组件接收props参数时定义数据接口&#xff1f;5.使用TypeScript开发react项目&#xff1a;6.要使一个组…...

ChatGPT 串接到 Discord - 团队协作好助理

ChatGPT 串接到 Discord - 团队协作好助理 ChatGPT 是由 OpenAI 开发的一个强大的语言模型&#xff0c;本篇文章教你如何串接 Discord Bot &#xff0c;协助团队在工作上更加高效并促进沟通与协作。使 ChatGPT 发挥出最大的功效&#xff0c;进一步提升工作效率和团队协作能力。…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

【论文笔记】若干矿井粉尘检测算法概述

总的来说&#xff0c;传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度&#xff0c;通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

七、数据库的完整性

七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

Linux系统部署KES

1、安装准备 1.版本说明V008R006C009B0014 V008&#xff1a;是version产品的大版本。 R006&#xff1a;是release产品特性版本。 C009&#xff1a;是通用版 B0014&#xff1a;是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存&#xff1a;1GB 以上 硬盘&#xf…...

Leetcode33( 搜索旋转排序数组)

题目表述 整数数组 nums 按升序排列&#xff0c;数组中的值 互不相同 。 在传递给函数之前&#xff0c;nums 在预先未知的某个下标 k&#xff08;0 < k < nums.length&#xff09;上进行了 旋转&#xff0c;使数组变为 [nums[k], nums[k1], …, nums[n-1], nums[0], nu…...

用 Rust 重写 Linux 内核模块实战:迈向安全内核的新篇章

用 Rust 重写 Linux 内核模块实战&#xff1a;迈向安全内核的新篇章 ​​摘要&#xff1a;​​ 操作系统内核的安全性、稳定性至关重要。传统 Linux 内核模块开发长期依赖于 C 语言&#xff0c;受限于 C 语言本身的内存安全和并发安全问题&#xff0c;开发复杂模块极易引入难以…...

2.2.2 ASPICE的需求分析

ASPICE的需求分析是汽车软件开发过程中至关重要的一环&#xff0c;它涉及到对需求进行详细分析、验证和确认&#xff0c;以确保软件产品能够满足客户和用户的需求。在ASPICE中&#xff0c;需求分析的关键步骤包括&#xff1a; 需求细化&#xff1a;将从需求收集阶段获得的高层需…...