在上一篇Netty 心跳 demo 中,瞭解了Netty中的客戶端和服務端之間的心跳。這篇就來說講Netty中的粘包和拆包以及相應的處理。java
粘包: 會將消息粘粘起來發送。相似吃米飯,一口吃多個飯粒,而不是一粒一粒的吃。
拆包: 會將消息拆開,分爲屢次接受。相似喝飲料,一口一口的喝,而不是一口氣喝完。git
簡單的來講:
屢次發送較少內容,會發生粘包現象。
單次發送內容過多,會發生拆包現象。github
咱們使用簡單的Netty的服務端和客戶端demo用來測試粘包和拆包。
將 Hello Netty 發送一百次,就會發送粘包現象;
將《春江花月夜》和《行路難》發送一次就會發送拆包現象;bootstrap
由於Netty已經提供了幾個經常使用的解碼器,幫助咱們解決這些問題,因此咱們沒必要再去造輪子了,直接拿來用就行了。服務器
在Server服務端使用定長數據幀的解碼器 FixedLengthFrameDecoder以後。
能夠明顯看到數據已經按照咱們所設定的大小分割了。
markdown
在Server服務端使用字節解碼器 LineBasedFrameDecoder 以後。
因爲字節已經超過咱們設置的最大的字節數,因此報錯了。
socket
因此,咱們發送的字節在設置的範圍內的話,就能夠看到拆包現象已經解決。
ide
Netty還提供了一個HttpObjectAggregator類用於解決粘包、拆包現象。
如下摘自Netty官方文檔oop
若是對於單條HTTP消息你不想處理多個消息對象,你能夠傳入 HttpObjectAggregator 到pipline中。HttpObjectAggregator 會將多個消息對象轉變爲單個 FullHttpRequest 或者 FullHttpResponse。
使用HttpObjectAggregator 以後
測試
能夠看到,粘包和拆包現象獲得了改善。
那麼開始貼代碼,幾乎和以前的demo同樣。
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/** * * Title: NettyServer * Description: Netty服務端 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyServer {
private static final int port = 6789; //設置服務端端口
private static EventLoopGroup group = new NioEventLoopGroup(); // 經過nio方式來接收鏈接和處理鏈接
private static ServerBootstrap b = new ServerBootstrap();
/** * Netty建立所有都是實現自AbstractBootstrap。 * 客戶端的是Bootstrap,服務端的則是 ServerBootstrap。 **/
public static void main(String[] args) throws InterruptedException {
try {
b.group(group);
b.channel(NioServerSocketChannel.class);
b.childHandler(new NettyServerFilter()); //設置過濾器
// 服務器綁定端口監聽
ChannelFuture f = b.bind(port).sync();
System.out.println("服務端啓動成功,端口是:"+port);
// 監聽服務器關閉監聽
f.channel().closeFuture().sync();
}catch(Exception e){
e.printStackTrace();
}
finally {
group.shutdownGracefully(); //關閉EventLoopGroup,釋放掉全部資源包括建立的線程
}
}
}
mport io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/** * * Title: HelloServerInitializer * Description: Netty 服務端過濾器 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyServerFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
// 解碼和編碼,應和客戶端一致
// ph.addLast(new FixedLengthFrameDecoder(100)); //定長數據幀的解碼器 ,每幀數據100個字節就切分一次。 用於解決粘包問題
// ph.addLast(new LineBasedFrameDecoder(2048)); //字節解碼器 ,其中2048是規定一行數據最大的字節數。 用於解決拆包問題
ph.addLast("aggregator", new HttpObjectAggregator(10*1024*1024));
ph.addLast("decoder", new StringDecoder());
ph.addLast("encoder", new StringEncoder());
ph.addLast("handler", new NettyServerHandler());// 服務端業務邏輯
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.net.InetAddress;
/** * * Title: HelloServerHandler * Description: 服務端業務邏輯 粘包、拆包測試 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/** 條數 */
private int count=0;
/** * 業務邏輯處理 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String body = (String)msg;
System.out.println("接受的數據是: " + body + ";條數是: " + ++count);
}
/** * 創建鏈接時,返回消息 */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("鏈接的客戶端地址:" + ctx.channel().remoteAddress());
ctx.writeAndFlush("客戶端"+ InetAddress.getLocalHost().getHostName() + "成功與服務端創建鏈接! ");
super.channelActive(ctx);
}
}
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.IOException;
/** * * Title: NettyClient * Description: Netty客戶端 粘包、拆包測試 * Version:1.0.0 * @author pancm * @date 2017年10月16日 */
public class NettyClient {
public static String host = "127.0.0.1"; //ip地址
public static int port = 6789; //端口
/// 經過nio方式來接收鏈接和處理鏈接
private static EventLoopGroup group = new NioEventLoopGroup();
private static Bootstrap b = new Bootstrap();
private static Channel ch;
/** * Netty建立所有都是實現自AbstractBootstrap。 * 客戶端的是Bootstrap,服務端的則是 ServerBootstrap。 **/
public static void main(String[] args) throws InterruptedException, IOException {
System.out.println("客戶端成功啓動...");
b.group(group);
b.channel(NioSocketChannel.class);
b.handler(new NettyClientFilter());
// 鏈接服務端
ch = b.connect(host, port).sync().channel();
star(3);
}
public static void star(int i) throws IOException{
String str="春江潮水連海平,海上明月共潮生。"
+" 灩灩隨波千萬裏,何處春江無月明! "
+" 江流宛轉繞芳甸,月照花林皆似霰;"
+" 空裏流霜不覺飛,汀上白沙看不見。"
+" 江天一色無纖塵,皎皎空中孤月輪。"
+" 江畔何人初見月?江月何年初照人?"
+" 人生代代無窮已,江月年年望類似。"
+" 不知江月待何人,但見長江送流水。"
+" 白雲一片去悠悠,青楓浦上不勝愁。"
+" 誰家今夜扁舟子?何處相思明月樓?"
+" 可憐樓上月徘徊,應照離人妝鏡臺。"
+" 玉戶簾中卷不去,搗衣砧上拂還來。"
+" 此時相望不相聞,願逐月華流照君。"
+" 鴻雁長飛光不度,魚龍潛躍水成文。"
+" 昨夜閒潭夢落花,可憐春半不還家。"
+" 江水流春去欲盡,江潭落月復西斜。"
+" 斜月沉沉藏海霧,碣石瀟湘無限路。"
+" 不知乘月幾人歸,落月搖情滿江樹。"
+" 噫籲嚱,危乎高哉!蜀道之難,難於上青天!蠶叢及魚鳧,開國何茫然!爾來四萬八千歲,不與秦塞通人煙。"
+" 西當太白有鳥道,能夠橫絕峨眉巔。地崩山摧壯士死,而後天梯石棧相鉤連。上有六龍回日之高標,下有衝波逆折之回川。"
+" 黃鶴之飛尚不得過,猿猱欲度愁攀援。青泥何盤盤,百步九折縈巖巒。捫參歷井仰脅息,以手撫膺坐長嘆。"
+" 問君西遊什麼時候還?畏途巉巖不可攀。但見悲鳥號古木,雄飛雌從繞林間。又聞子規啼夜月,愁空山。"
+" 蜀道之難,難於上青天,令人聽此凋朱顏!連峯去天不盈尺,枯鬆倒掛倚絕壁。飛湍瀑流爭喧豗,砯崖轉石萬壑雷。"
+" 其險也如此,嗟爾遠道之人胡爲乎來哉!劍閣崢嶸而崔嵬,一夫當關,萬夫莫開。"
+" 所守或匪親,化爲狼與豺。朝避猛虎,夕避長蛇;磨牙吮血,殺人如麻。錦城雖雲樂,不如早還家。"
+" 蜀道之難,難於上青天,側身西望長諮嗟!";
if(i==1){
for(int j=0;j<100;j++){
str="Hello Netty";
ch.writeAndFlush(str);
}
}else if(i==2){
str+=str;
ch.writeAndFlush(str);
}else if(i==3){
//System.getProperty("line.separator") 結束標記
byte [] bt=(str+System.getProperty("line.separator")).getBytes();
ByteBuf message = Unpooled.buffer(bt.length);
message.writeBytes(bt);
ch.writeAndFlush(message);
}
System.out.println("客戶端發送數據:"+str+",發送數據的長度:"+str.length());
}
}
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
/** * * Title: NettyClientFilter * Description: Netty客戶端 過濾器 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyClientFilter extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline ph = ch.pipeline();
/* * 解碼和編碼,應和服務端一致 * */
ph.addLast("decoder", new StringDecoder());
ph.addLast("encoder", new StringEncoder());
ph.addLast("handler", new NettyClientHandler()); //客戶端的邏輯
}
}
import java.util.Date;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/** * * Title: NettyClientHandler * Description: 客戶端業務邏輯實現 * Version:1.0.0 * @author pancm * @date 2017年10月8日 */
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/** * 業務邏輯處理 */
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("客戶端接受的消息:"+msg);
}
/** * 創建鏈接時 */
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("創建鏈接時:"+new Date());
ctx.fireChannelActive();
}
/** * * 關閉鏈接時 */
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("關閉鏈接時:"+new Date());
}
}
該項目我放在github上了,有興趣的能夠看看!https://github.com/xuwujing/Netty