Netty学习10-粘包和拆包

1 粘包拆包基本概念


TPC是一个面向流的协议。所谓流就是没有边界的一串数据,如同河水般连成一片,其中并没有分界线。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的具体情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆成多个包发送,也有可能把多个小包封装成一个包发送。这就是拆包和粘包的概念。

比如向对方发送信息:Good Morning Sit down please。先向对方问早,再请对方坐下。但实际情况有可能这样:
第一次接收:Good Morning Sit
第二次接收:down please


2 解决粘包拆包的途径


TCP是面向流的协议,消息中间没有明显的界限。那为了对消息进行区分,只能依靠上层的应用协议,往往采取如下方式:

[1] 消息长度固定。累计读取到长度总和为定长的LEN的报文后,就认为读取到一个完整的消息。将计数器置位,重新开始读取下个数据报。
[2] 将回车换行符作为消息结束符。如FTP协议,这种方式在文本协议中应用广泛。
[3] 将特殊的分隔符作为消息的结束标志。回车换行符就是一种特殊的结束符。
[4] 通过在消息头中定义长度字段来标识消息的总长度。

Netty提供了对应的解码器:LineBaseFrameDecoder、DelimiterBaseFrameDecoder、FixedLengthFrameDecoder等。具体示例参考《Netty权威指南》。下面分析一个粘包拆包的示例,和自定义解码器。


3 粘包拆包实例

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;

public class Server {
	public static void main(String[] args) {
		// 服务类
		ServerBootstrap bootstrap = new ServerBootstrap();
		// boss线程监听端口,worker线程负责数据读写
		ExecutorService boss = Executors.newCachedThreadPool();
		ExecutorService worker = Executors.newCachedThreadPool();
		// 设置niosocket工厂
		bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
		// 设置管道的工厂
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("handler1", new HelloHandler());
				return pipeline;
			}
		});
		bootstrap.bind(new InetSocketAddress(10101));
		System.out.println("start!!!");
	}
}

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;

public class HelloHandler extends SimpleChannelHandler {
	private int count = 1;
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		ChannelBuffer buffer = (ChannelBuffer) e.getMessage();
		byte[] array = buffer.array();
		System.out.println(new String(array) + "  " + count);
		count++;
	}
}

import java.net.Socket;
public class Client {
	public static void main(String[] args) throws Exception {
		Socket socket = new Socket("127.0.0.1", 10101);
		String message = "hello";
		for (int i = 0; i < 20; i++) {
			socket.getOutputStream().write(message.getBytes());
		}
		socket.close();
	}
}
输出结果1
hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello  1

输出结果2
hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohe 1
llohellohellohellohello 2



4 自定义处理器

在本例中采取了固定长度的方式解决粘包拆包问题,有以下几个显著的变化:
[1] 在Client发送时,设置了一个4字节的长度头,该长度头记录了内容的长度。
[2] Server端设置了MyDecoder继承自FrameDecoder。注释非常清晰。
[3] 在MyDecoder中对字节数组做了处理,包装成了String类型。所以下一个handler直接处理String类型即可。

import java.net.Socket;
import java.nio.ByteBuffer;
public class Client {
	public static void main(String[] args) throws Exception {
		Socket socket = new Socket("127.0.0.1", 10101);
		// 消息内容
		String message = "hello";
		byte[] bytes = message.getBytes();
		// 构造字节数组,长度为(4+内容长度)
		// 其中4个字节长度字段是int为4个字节
		ByteBuffer buffer = ByteBuffer.allocate(4 + bytes.length); 		
		// 设置长度字段(仅仅是内容的长度)
		buffer.putInt(bytes.length);
		// 设置内容
		buffer.put(bytes);
		
		byte[] array = buffer.array();
		for (int i = 0; i < 20; i++) {
			socket.getOutputStream().write(array);
		}
		socket.close();
	}
}

import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.jboss.netty.bootstrap.ServerBootstrap;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
public class Server {

	public static void main(String[] args) {
		//服务类
		ServerBootstrap bootstrap = new ServerBootstrap();	
		//boss线程监听端口,worker线程负责数据读写
		ExecutorService boss = Executors.newCachedThreadPool();
		ExecutorService worker = Executors.newCachedThreadPool();
		//设置niosocket工厂
		bootstrap.setFactory(new NioServerSocketChannelFactory(boss, worker));
		//设置管道的工厂
		bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
			@Override
			public ChannelPipeline getPipeline() throws Exception {
				ChannelPipeline pipeline = Channels.pipeline();
				pipeline.addLast("decoder", new MyDecoder());
				pipeline.addLast("handler1", new HelloHandler());
				return pipeline;
			}
		});
		bootstrap.bind(new InetSocketAddress(10101));	
		System.out.println("start!!!");
	}
}

import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.handler.codec.frame.FrameDecoder;
public class MyDecoder extends FrameDecoder {

	@Override
	protected Object decode(ChannelHandlerContext ctx, Channel channel,
			ChannelBuffer buffer) throws Exception {
		// 基本长度(至少要有长度头那么长)
		int baseLength = 4;
		if (buffer.readableBytes() > baseLength) {
			// 防止Socket攻击
			if (buffer.readableBytes() > 2048) {
				buffer.skipBytes(buffer.readableBytes());
			}
			// 标记
			buffer.markReaderIndex();
			// 长读取度头
			int length = buffer.readInt();
			// 长度不够
			if (buffer.readableBytes() < length) {
				// 还原到上述标记位置
				buffer.resetReaderIndex();
				// 缓存当前剩余的buffer数据,等待剩下数据包到来
				return null;
			}
			// 读数据
			byte[] bytes = new byte[length];
			buffer.readBytes(bytes);
			// 往下传递对象
			return new String(bytes);
		}
		// 缓存当前剩余的buffer数据,等待剩下数据包到来
		return null;
	}

}

import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
public class HelloHandler extends SimpleChannelHandler {
	private int count = 1;
	@Override
	public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
			throws Exception {
		System.out.println(e.getMessage() + "  " +count);
		count++;
	}
}

发布了535 篇原创文章 · 获赞 1162 · 访问量 450万+

猜你喜欢

转载自blog.csdn.net/woshixuye/article/details/54015683
今日推荐