Netty学习10-粘包和拆包

来源:互联网 发布:php curl 超时处理 编辑:程序博客网 时间:2024/06/05 03:58

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() {@Overridepublic 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;@Overridepublic 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() {@Overridepublic 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 {@Overrideprotected 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;@Overridepublic void messageReceived(ChannelHandlerContext ctx, MessageEvent e)throws Exception {System.out.println(e.getMessage() + "  " +count);count++;}}

1 0