一.Netty介紹
1.什么是netty
Netty 是由 JBOSS 提供的一個(gè) Java 開源框架钾唬。Netty 提供異步的堰乔、基于事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用程序框架偏化,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò) IO 程序,是目前最流行的 NIO 框架镐侯,Netty 在互聯(lián)網(wǎng)領(lǐng)域侦讨、大數(shù)據(jù)分布式計(jì)算領(lǐng)域、游戲行業(yè)、通信行業(yè)等獲得了廣泛的應(yīng)用韵卤,知名的 Elasticsearch 骗污、Dubbo 框架內(nèi)部都采用了 Netty。
2.為什么要用netty
原生 NIO 存在問(wèn)題:
1.NIO 的類庫(kù)和 API 繁雜
2.需要熟悉 Java 多線程編程怜俐,因?yàn)?NIO 編程涉及到 Reactor 模式,必須對(duì)多線程和網(wǎng)絡(luò)編程非常熟悉邓尤, 才能編寫出高質(zhì)量的 NIO 程序
3.開發(fā)工作量和難度都非常大拍鲤。例如客戶端面臨斷連重連、網(wǎng)絡(luò)閃斷汞扎、半包讀寫季稳、失敗緩存、網(wǎng)絡(luò)擁塞和異常 流的處理等等處理起來(lái)難度會(huì)比較大澈魄。
4.JDK NIO 的 Bug:例如臭名昭著的 Epoll Bug景鼠,它會(huì)導(dǎo)致 Selector 空輪詢,最終導(dǎo)致 CPU 100%痹扇。直到 JDK 1.7 版本該問(wèn)題仍舊存在铛漓,沒(méi)有被根本解決。
3.Netty的優(yōu)點(diǎn)
Netty 對(duì) JDK 自帶的 NIO 的 API 進(jìn)行了封裝鲫构,解決了上述問(wèn)題浓恶。
1.設(shè)計(jì)優(yōu)雅:適用于各種傳輸類型的統(tǒng)一 API 阻塞和非阻塞 Socket;基于靈活且可擴(kuò)展的事件模型结笨,可以清晰地分離關(guān)注點(diǎn)包晰;高度可定制的線程模型 - 單線程,一個(gè)或多個(gè)線程池.
2.使用方便:詳細(xì)記錄的 Javadoc炕吸,用戶指南和示例伐憾;沒(méi)有其他依賴項(xiàng),JDK 5(Netty 3.x)或 6(Netty 4.x)就足夠了赫模。
3.高性能树肃、吞吐量更高:延遲更低;減少資源消耗瀑罗;最小化不必要的內(nèi)存復(fù)制扫外。
4.安全:完整的 SSL/TLS 和 StartTLS 支持。
5.社區(qū)活躍廓脆、不斷更新:社區(qū)活躍筛谚,版本迭代周期短,發(fā)現(xiàn)的 Bug 可以被及時(shí)修復(fù)停忿,同時(shí)更多的新功能會(huì)被加入
二.Reactor三種線程模型
1.現(xiàn)有的三種線程模型
不同的線程模式驾讲,對(duì)程序的性能有很大影響,目前存在的線程模型有:
①.傳統(tǒng)阻塞 I/O 服務(wù)模型
②.Reactor 模式
Reactor 模式又有 3 種典型的實(shí)現(xiàn)
單 Reactor 單線程;
單 Reactor 多線程吮铭;
主從 Reactor 多線程
Netty 的線程模型是主要是基于主從 Reactor 多線程模型改成了主從 Reactor 多線程模型有多個(gè) Reactor模式
2.傳統(tǒng)阻塞 I/O 服務(wù)模型介紹
特點(diǎn):
采用阻塞IO模式獲取輸入的數(shù)據(jù)
每個(gè)連接都需要?jiǎng)?chuàng)建單獨(dú)的線程完成數(shù)據(jù)的輸入时迫,業(yè)務(wù)處理和數(shù)據(jù)的返回
缺點(diǎn):
當(dāng)并發(fā)數(shù)很大,就會(huì)創(chuàng)建大量的線程谓晌,占用很大系統(tǒng)資源掠拳,在線程開銷和上下文切換上降低處理性能
當(dāng)連接創(chuàng)建后,如果當(dāng)前線程暫時(shí)沒(méi)有數(shù)據(jù)可讀纸肉,該線程會(huì)阻塞在read 操作溺欧,造成線程資源的浪費(fèi)。
黃色的框表示對(duì)象柏肪, 藍(lán)色的框表示線程 白色的框表示方法(API)
3. Reactor 模式
針對(duì)傳統(tǒng)阻塞 I/O 服務(wù)模型的 2 個(gè)缺點(diǎn)姐刁,解決方案:
I/O 復(fù)用模型:多個(gè)連接共用一個(gè)阻塞對(duì)象,應(yīng)用程序只需要在一個(gè)阻塞對(duì)象上等待烦味,無(wú)需阻塞等待所有連接聂使。當(dāng)某個(gè)連接有新的數(shù)據(jù)可以處理時(shí),操作系統(tǒng)通知應(yīng)用程序谬俄,線程從阻塞狀態(tài)返回柏靶,開始進(jìn)行業(yè)務(wù)處理。
Reactor 對(duì)應(yīng)的叫法: 1. 反應(yīng)器模式 2. 分發(fā)者模式(Dispatcher) 3. 通知者模式(notifier)
基于線程池復(fù)用線程資源模式:不必再為每個(gè)連接創(chuàng)建線程溃论,將連接完成后的業(yè)務(wù)處理任務(wù)分配給線程進(jìn)行處理宿礁,一個(gè)線程可以處理多個(gè)連接的業(yè)務(wù)。
I/O 復(fù)用結(jié)合線程池蔬芥,就是 Reactor 模式基本設(shè)計(jì)思想
Reactor 模式梆靖,通過(guò)一個(gè)或多個(gè)輸入同時(shí)傳遞給服務(wù)處理器的模式,(基于事件驅(qū)動(dòng))
服務(wù)器端程序處理傳入的多個(gè)請(qǐng)求,并將它們同步分派到相應(yīng)的處理線程, 因此Reactor模式也叫 Dispatcher模式
Reactor 模式使用IO復(fù)用監(jiān)聽事件, 收到事件后笔诵,分發(fā)給某個(gè)線程(進(jìn)程), 這點(diǎn)就是網(wǎng)絡(luò)服務(wù)器高并發(fā)處理關(guān)鍵
4.單 Reactor 單線程
1.工作原理:
①Select 是前面 I/O 復(fù)用模型介紹的標(biāo)準(zhǔn)網(wǎng)絡(luò)編程 API返吻,可以實(shí)現(xiàn)應(yīng)用程序通過(guò)一個(gè)阻塞對(duì)象監(jiān)聽多路連接請(qǐng)求
②Reactor 對(duì)象通過(guò) Select 監(jiān)控客戶端請(qǐng)求事件,收到事件后通過(guò) Dispatch 進(jìn)行分發(fā)
③如果是建立連接請(qǐng)求事件乎婿,則由 Acceptor 通過(guò) Accept 處理連接請(qǐng)求测僵,然后創(chuàng)建一個(gè) Handler 對(duì)象處理連接完成后的后續(xù)業(yè)務(wù)處理
④如果不是建立連接事件,則 Reactor 會(huì)分發(fā)調(diào)用連接對(duì)應(yīng)的 Handler 來(lái)響應(yīng)
⑤Handler 會(huì)完成 Read→業(yè)務(wù)處理→Send 的完整業(yè)務(wù)流程
2.優(yōu)點(diǎn):
模型簡(jiǎn)單谢翎,沒(méi)有多線程捍靠、進(jìn)程通信、競(jìng)爭(zhēng)的問(wèn)題森逮,全部都在一個(gè)線程中完成
3.缺點(diǎn):
①性能問(wèn)題榨婆,只有一個(gè)線程,無(wú)法完全發(fā)揮多核 CPU 的性能褒侧。
②可靠性問(wèn)題良风,線程意外終止谊迄,或者進(jìn)入死循環(huán),會(huì)導(dǎo)致整個(gè)系統(tǒng)通信模塊不可用烟央,不能接收和處理外部消息统诺,造成節(jié)點(diǎn)故障
③服務(wù)器端用一個(gè)線程通過(guò)多路復(fù)用搞定所有的 IO 操作(包括連接,讀疑俭、寫等)粮呢,編碼簡(jiǎn)單,清晰明了钞艇,但是如果客戶端連接數(shù)量較多時(shí)啄寡,當(dāng)對(duì)應(yīng)多個(gè)讀時(shí),還是會(huì)出現(xiàn)阻塞現(xiàn)象香璃,當(dāng)這種情況發(fā)生時(shí)將無(wú)法支撐高并發(fā)的場(chǎng)景这难。
4.應(yīng)用場(chǎng)景:
客戶端的數(shù)量有限舟误,業(yè)務(wù)處理非称厦耄快速(比如 Redis在業(yè)務(wù)處理的時(shí)間復(fù)雜度 O(1) 的情況)
5.單Reactor多線程
1.工作原理:
①Reactor 對(duì)象通過(guò)select 監(jiān)控客戶端請(qǐng)求事件, 收到事件后,通過(guò)dispatch進(jìn)行分發(fā)
②如果是建立連接請(qǐng)求, 則由Acceptor 通過(guò)accept 處理連接請(qǐng)求, 然后創(chuàng)建一個(gè)Handler對(duì)象處理完成連接后的各種事件
③如果不是連接請(qǐng)求嵌溢,則由Reactor分發(fā)調(diào)用連接對(duì)應(yīng)的handler 來(lái)處理
④handler 只負(fù)責(zé)響應(yīng)事件眯牧,不做具體的業(yè)務(wù)處理, 通過(guò)read 讀取數(shù)據(jù)后,會(huì)分發(fā)給后面的worker線程池的某個(gè)線程處理業(yè)務(wù)赖草。
⑤worker 線程池會(huì)分配獨(dú)立線程完成真正的業(yè)務(wù)学少,并將結(jié)果返回給handler,handler收到響應(yīng)后,通過(guò)send 將結(jié)果返回給client.
2.優(yōu)點(diǎn):
可以充分的利用多核cpu 的處理能力
3.缺點(diǎn):
多線程數(shù)據(jù)共享和訪問(wèn)比較復(fù)雜,Reactor處理所有的事件的監(jiān)聽和響應(yīng)秧骑,在單線程運(yùn)行時(shí)版确,在高并發(fā)場(chǎng)景容易出現(xiàn)性能瓶頸.
6.主從 Reactor 多線程
1.工作原理:
①Reactor主線程 MainReactor 對(duì)象通過(guò)select 監(jiān)聽連接事件, 收到事件后,通過(guò)Acceptor 處理連接事件
②當(dāng) Acceptor 處理連接事件后乎折,MainReactor 將連接分配給SubReactor
③subReactor 將連接加入到連接隊(duì)列進(jìn)行監(jiān)聽,并創(chuàng)建handler進(jìn)行各種事件處理
④當(dāng)有新事件發(fā)生時(shí)绒疗, subreactor 就會(huì)調(diào)用對(duì)應(yīng)的handler處理
⑤handler 通過(guò)read 讀取數(shù)據(jù),分發(fā)給后面的worker 線程處理
⑥worker 線程池分配獨(dú)立的worker 線程進(jìn)行業(yè)務(wù)處理骂澄,并返回結(jié)果
⑦h(yuǎn)andler 收到響應(yīng)的結(jié)果后吓蘑,再通過(guò)send 將結(jié)果返回給client
⑧Reactor 主線程可以對(duì)應(yīng)多個(gè)Reactor 子線程, 即MainRecator 可以關(guān)聯(lián)多個(gè)SubReactor
三.Netty線程模型
1.工作原理
Netty抽象出兩組線程池 BossGroup 專門負(fù)責(zé)接收客戶端的連接, WorkerGroup 專門負(fù)責(zé)網(wǎng)絡(luò)的讀寫
BossGroup 和 WorkerGroup 類型都是 NioEventLoopGroup
NioEventLoopGroup 相當(dāng)于一個(gè)事件循環(huán)組, 這個(gè)組中含有多個(gè)事件循環(huán) ,每一個(gè)事件循環(huán)是 NioEventLoop
NioEventLoop 表示一個(gè)不斷循環(huán)的執(zhí)行處理任務(wù)的線程坟冲, 每個(gè)NioEventLoop 都有一個(gè)selector , 用于監(jiān)聽綁定在該通道上的socket的網(wǎng)絡(luò)通訊
NioEventLoopGroup 可以有多個(gè)線程, 即可以含有多個(gè)NioEventLoop
每個(gè)Boss NioEventLoop 循環(huán)執(zhí)行的步驟有3步
輪詢accept 事件
處理accept 事件 , 與client端建立連接 , 生成NioScocketChannel , 并將其注冊(cè)到某個(gè)worker NIOEventLoop 上的 selector 上
處理任務(wù)隊(duì)列的任務(wù) 磨镶, 即 runAllTasks
每個(gè) Worker NIOEventLoop 循環(huán)執(zhí)行的步驟
輪詢r(jià)ead, write 事件
處理i/o事件, 即read , write 事件健提,在對(duì)應(yīng)NioScocketChannel 處理
處理任務(wù)隊(duì)列的任務(wù) 琳猫, 即 runAllTasks
每個(gè)Worker NIOEventLoop 處理業(yè)務(wù)時(shí),會(huì)使用pipeline(管道), pipeline 中包含了boss group上NioEventLoop注冊(cè)到worker 的selector 的channel , 即通過(guò)pipeline 可以獲取到對(duì)應(yīng)通道, 管道中維護(hù)了很多的處理器
四.Netty入門
1.引入java包
(JDK 5(Netty 3.x)或 6(Netty 4.x))
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.20.Final</version>
</dependency>
2.hello world 編寫
入門的編寫一共需要4個(gè)類
2.1.netty server 端編寫
package com.zpb.netty.netty.helloWorld;
?
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.NioEventLoop;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
?
/**
* @dec : netty入門
* @Date: 2019/11/24
* @Auther: pengbo.zhao
* @version: 1.0
* @demand:
*
* {@link #main(String[] args)}
*
*/
public class NettyServer {
?
public static void main(String[] args) throws Exception{
?
//1.創(chuàng)建BossGroup 和 WorkerGroup
//1.1 創(chuàng)建2個(gè)線程組
//bossGroup只處理連接請(qǐng)求
//workerGroup 處理客戶端的業(yè)務(wù)邏輯
//2個(gè)都是無(wú)限循環(huán)
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
?
//2.創(chuàng)建服務(wù)端的啟動(dòng)對(duì)象,可以為服務(wù)端啟動(dòng)配置一些服務(wù)參數(shù)
ServerBootstrap bootStrap = new ServerBootstrap();
?
//2.1使用鏈?zhǔn)骄幊虂?lái)配置服務(wù)參數(shù)
bootStrap.group(bossGroup,workerGroup) //設(shè)置2個(gè)線程組
.channel(NioServerSocketChannel.class) //使用NioServerSocketChannel作為服務(wù)器的通道
.option(ChannelOption.SO_BACKLOG,128) //設(shè)置線程等待的連接個(gè)數(shù)
.childOption(ChannelOption.SO_KEEPALIVE,Boolean.TRUE) //設(shè)置保持活動(dòng)連接狀態(tài)
.childHandler(new ChannelInitializer<SocketChannel>() {
//給PipeLine設(shè)置處理器
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//通過(guò)socketChannel得到pipeLine私痹,然后向pipeLine中添加處理的handle
socketChannel.pipeline().addLast(new NettyServerHandle());
}
}); //給workerGroup 的EventLoop對(duì)應(yīng)的管道設(shè)置處理器(可以自定義/也可使用netty的)
System.err.println("server is ready......");
?
//啟動(dòng)服務(wù)器沸移,并綁定1個(gè)端口且同步生成一個(gè)ChannelFuture 對(duì)象
ChannelFuture channelFuture = bootStrap.bind(8888).sync();
?
//對(duì)關(guān)閉通道進(jìn)行監(jiān)聽(netty異步模型)
//當(dāng)通道進(jìn)行關(guān)閉時(shí)痪伦,才會(huì)觸發(fā)這個(gè)關(guān)閉動(dòng)作
channelFuture.channel().closeFuture().sync();
?
}
}
2.2.netty server handler編寫
package com.zpb.netty.netty.helloWorld;
?
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.util.CharsetUtil;
?
/**
* @dec :
* @Date: 2019/11/24
* @Auther: pengbo.zhao
* @version: 1.0
* @demand:
*/
public class NettyServerHandle extends ChannelInboundHandlerAdapter {
/**
* 讀取數(shù)據(jù)
*
* @param: 1.ChannelHandlerContext ctx:上下文對(duì)象, 含有 管道 pipeline , 通道 channel, 地址
* @param: 2\. Object msg: 就是客戶端發(fā)送的數(shù)據(jù) 默認(rèn) Object
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.err.println("服務(wù)器讀取線程 " + Thread.currentThread().getName());
System.out.println("server ctx =" + ctx);
System.out.println("看看 channel 和 pipeline 的關(guān)系");
?
Channel channel = ctx.channel();
ChannelPipeline pipeline = ctx.pipeline(); //本質(zhì)是一個(gè)雙向鏈接, 出站入站
?
//將 msg 轉(zhuǎn)成一個(gè) ByteBuf,ByteBuf 是 Netty 提供的,不是 NIO 的 ByteBuffer.
ByteBuf buf = (ByteBuf) msg;
System.out.println("客戶端發(fā)送消息是:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("客戶端地址:" + channel.remoteAddress());
}
?
/**
* 讀取數(shù)據(jù)完成后
*
* @param:
* @return:
* @auther:
* @date:
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//writeAndFlush 是 write + flush
//將數(shù)據(jù)寫入到緩存雹锣,并刷新
//一般講网沾,我們對(duì)這個(gè)發(fā)送的數(shù)據(jù)進(jìn)行編碼
ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客戶端~(>^ω^<)喵", CharsetUtil.UTF_8));
}
?
//處理異常, 一般是需要關(guān)閉通道
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
2.3.netty client端編寫
package com.zpb.netty.netty.helloWorld;
?
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;
?
?
/**
* @dec :
* @Date: 2019/11/24
* @Auther: pengbo.zhao
* @version: 1.0
* @demand:
*/
public class NettyClient {
?
public static void main(String[] args) throws Exception {
?
//1.客戶端定義一個(gè)循環(huán)事件組
EventLoopGroup group = new NioEventLoopGroup();
?
try {
?
//2.創(chuàng)建客戶端啟動(dòng)對(duì)象
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group) //設(shè)置線程組
.channel(NioSocketChannel.class) //設(shè)置客戶端通道實(shí)現(xiàn)類
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandle());
}
});
System.err.println("client is ready......");
?
//3.啟動(dòng)客戶端去連接服務(wù)端
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
?
//4.設(shè)置通道關(guān)閉監(jiān)聽(當(dāng)監(jiān)聽到通道關(guān)閉時(shí),關(guān)閉client)
channelFuture.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
2.4.netty client handler端編寫
package com.zpb.netty.netty.helloWorld;
?
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
?
/**
* @dec :
* @Date: 2019/11/24
* @Auther: pengbo.zhao
* @version: 1.0
* @demand:
*/
public class NettyClientHandle extends ChannelInboundHandlerAdapter{
?
//如果client 端服務(wù)啟動(dòng)完成后
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
?
System.err.println("client "+ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("hello,netty server...",CharsetUtil.UTF_8));
}
?
//當(dāng)通道有讀事件時(shí)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
?
ByteBuf byteBuf = (ByteBuf) msg;
System.err.println("服務(wù)器端回復(fù)消息:"+byteBuf.toString(CharsetUtil.UTF_8));
System.err.println("服務(wù)器端地址是:"+ctx.channel().remoteAddress());
}
?
//當(dāng)通道有異常時(shí)
?
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
五.Netty三種任務(wù)隊(duì)列的使用
當(dāng)我們?cè)谔幚淼膆andle中如果出現(xiàn)了阻塞的情況蕊爵,或者處理業(yè)務(wù)邏輯比較耗時(shí)辉哥,我們不能讓程序處于阻塞,
當(dāng)有客戶端請(qǐng)求時(shí)攒射,我們想讓程序定時(shí)的去執(zhí)行業(yè)務(wù)邏輯醋旦,
當(dāng)需要對(duì)一些用戶需要進(jìn)行推送活動(dòng)時(shí),根據(jù)用戶標(biāo)識(shí)会放,找到對(duì)應(yīng)的 Channel 引用饲齐,向該用戶推送特定消息時(shí)
可以采用以下三種任務(wù)隊(duì)列:
1.提交到execute(Runnable command)中時(shí)
ctx.channel().eventLoop().execute(new Runnable() { })
?
業(yè)務(wù)邏輯交給線程去處理,線程不會(huì)阻塞在這里,而是直接返回咧最,直到有數(shù)據(jù)才返回給客戶端捂人,如果有多個(gè)線程runnable需要處理,那么只能等上一個(gè)處理完才會(huì)處理下一個(gè)矢沿,(假如第1個(gè)任務(wù)需要10S,第2個(gè)需要20s,執(zhí)行完共需30S)
2.提交到 scheduledTaskQueue中
schedule(Runnable command, long delay, TimeUnit unit)
?
①Runnable command:執(zhí)行業(yè)務(wù)邏輯處理的線程
?
② long delay:定時(shí)時(shí)長(zhǎng)
?
③TimeUnit unit:定時(shí)類型
?
業(yè)務(wù)邏輯交給定時(shí)線程去處理滥搭。
3.通過(guò)傳輸?shù)膬?nèi)容的標(biāo)識(shí)
在解碼客戶端發(fā)送的內(nèi)容中,讀取到客戶端的特殊標(biāo)識(shí)捣鲸,利用這個(gè)標(biāo)識(shí)來(lái)進(jìn)行推送消息處理瑟匆,這個(gè)在粘包、拆包中進(jìn)行說(shuō)明