Netty:解碼器

一、解決粘包拆包問題的方法

   當客戶端向服務端發送了一個大的數據包時(如600M),TCP幾乎不會一次把這個包完整的發送到服務端,TCP分把這個包分包,分幾次發給服務端,至於TCP要分多少次,這是不可預測的。應用層如何正確的去區別每一條信息呢?下面介紹第一種分包的方法:以回車換行符作爲消息的結束符,FTP也是採用這種方式,這個結束符也有點像CString`\0`.

  二、利用結束符解包的思路

   分析過程:

   1.消息中包含回車換行符(\n\r\n),否則這條消息是不合規則的

   2.獲取到回車換行符的位置,得到這個位置後,就可以知道這個消息的 長度,根據這個長度讀取數據.

如下圖:

  +


Server;

/********************************************************************
 * 
 ********************************************************************
 * Netty 學習
 ********************************************************************
 */
package com.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 *
 * @author Jack Lei
 * @email [email protected]
 * @date 2016年3月13日 下午7:58:09
 */
public class Server {
	public Server(int port) {
		EventLoopGroup boss = new NioEventLoopGroup();
		EventLoopGroup work = new NioEventLoopGroup();

		ServerBootstrap boot = new ServerBootstrap();
		boot.group(boss, work)
			.channel(NioServerSocketChannel.class)
			.option(ChannelOption.TCP_NODELAY, true)
			.childHandler(new ServerChannelInitializer());
		try {
			ChannelFuture future = boot.bind(port).sync();
			future.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			boss.shutdownGracefully();
			work.shutdownGracefully();
		}
	}

	public static void main(String[] args) {
		new Server(8090);
	}
}

/********************************************************************
 * 
 ********************************************************************
 * Netty 學習
 ********************************************************************
 */
package com.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;

import com.netty.codec.PacketDecoder;
import com.netty.handler.ServerHandler;

/**
 *
 * @author Jack Lei
 * @email [email protected]
 * @date 2016年3月16日 上午1:28:55
 */
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {

	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		ch.pipeline()
			.addLast(new PacketDecoder(30))
			.addLast(new ServerHandler());
	}

}

/********************************************************************
 * 
 ********************************************************************
 * Netty 學習
 ********************************************************************
 */
package com.netty.handler;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

/**
 *
 * @author Jack Lei
 * @email [email protected]
 * @date 2016年3月16日 上午1:30:03
 */
public class ServerHandler extends ChannelInboundHandlerAdapter {
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		ByteBuf buf = (ByteBuf) msg;
		short packetId = buf.readShort();
		int bodySize = buf.readableBytes();
		byte[] body = new byte[bodySize];
		buf.readBytes(body);
		System.out.println("recive packet from client packetId = " + packetId + " , body = " + new String(body));
	}

}


/********************************************************************
 * 
 ********************************************************************
 * Netty 學習
 ********************************************************************
 */
package com.netty.codec;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 *
 * @author Jack Lei
 * @email [email protected]
 * @date 2016年3月16日 上午1:31:08
 */
public class PacketDecoder extends ByteToMessageDecoder {
	/* 一條消息的最大長度* */
	private int	lineLength;

	public PacketDecoder(int lineLenth) {
		this.lineLength = lineLenth;
	}

	@Override
	protected void decode(	ChannelHandlerContext ctx,
							ByteBuf in,
							List<Object> out) throws Exception {
		int endPostion = findEndPostion(in);

		if (endPostion != -1) {
			int endWordLength = in.getByte(endPostion) == '\n' ? 1 : 2;
			int bodySize = (endPostion - in.readerIndex()) + endWordLength;
			out.add(in.readBytes(bodySize));
		} else {
			// 在指定消息長度的內沒有讀到結束符,就說明讀到的是不符合規則的數據包
			if (in.readableBytes() >= lineLength) {
				throw new Exception("消息不完整。");
			}
		}

	}

	private int findEndPostion(ByteBuf buf) {
		int size = buf.writerIndex();
		for (int index = buf.readerIndex(); index < size; index++) {
			if (buf.getByte(index) == '\n') {
				return index;
				// 13 10
			} else if (buf.getByte(index) == '\r' && index < size - 1 && buf.getByte(index + 1) == '\n') {
				return index;
			}
		}
		return -1;
	}
}

Client:

/********************************************************************
 * 
 ********************************************************************
 * Netty 學習
 ********************************************************************
 */
package com.netty;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
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;

/**
 *
 * @author Jack Lei
 * @email [email protected]
 * @date 2016年3月13日 下午8:05:24
 */
public class Client {
	final short	packetId	= 10001;

	public Client(String host, int port) {
		EventLoopGroup work = new NioEventLoopGroup();
		Bootstrap boot = new Bootstrap();
		boot.group(work)
			.channel(NioSocketChannel.class)
			.handler(new ChannelInitializer<SocketChannel>() {
				@Override
				protected void initChannel(SocketChannel ch) throws Exception {
					ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
						@Override
						public void channelActive(ChannelHandlerContext ctx) throws Exception {
							for (int i = 0; i < 12000; i++) {
								ByteBuf buf = Unpooled.buffer();
								buf.writeShort(packetId);
								// 每條請求消息以換行符分隔
								String body = "測試請求->" + i + System.getProperty("line.separator");
								buf.writeBytes(body.getBytes());
								System.out.println("send packet to Server packetId " + packetId + ", size = " + (body.getBytes().length + 2) + " , body = " + body);
								ctx.writeAndFlush(buf);
							}
						}


					});
				}
			});
		try {
			ChannelFuture future = boot.connect(host, port).sync();
			future.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			work.shutdownGracefully();
		}

	}

	public static void main(String[] args) {
		new Client("127.0.0.1", 8090);
	}
}


  三、LineBaseFrameDecoder

LineBaseFrameDecoder,Netty提供的一個可以解決TCP粘包拆包的類,也是以回車換行符作爲消息結束的標誌,經常搭配StringDecoder一起使用。爲了方便理解解碼器,我寫的PacketDecoder就是簡化了LineBaseFrameDecoder.關於解碼器還有其他幾種寫法,如限定消息的長度,通過定義消息長度來標識消息的長度,想了解的可以看下這幾個類的源碼(DelimiterBaseFrameDecoder,FixedLengthFrameDecoder.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章