Netty解決TCP的粘包和分包(一)

Netty解決TCP的粘包和分包(一)java

關於TCP的粘包和分包:http://my.oschina.net/xinxingegeya/blog/484824git

Netty分包

分包的解決辦法:github

一、消息定長,報文大小固定長度,不夠空格補全,發送和接收方遵循相同的約定,這樣即便粘包了經過接收方編程實現獲取定長報文也能區分。編程

二、包尾添加特殊分隔符,例如每條報文結束都添加回車換行符(例如FTP協議)或者指定特殊字符做爲報文分隔符,接收方經過特殊分隔符切分報文區分。bootstrap

三、將消息分爲消息頭和消息體,消息頭中包含表示信息的總長度(或者消息體長度)的字段數組

四、更復雜的自定義應用層協議服務器

而在netty提供了兩個解碼器,能夠進行分包的操做,這兩個解碼器分別是:
網絡

  • DelimiterBasedFrameDecoder(添加特殊分隔符報文來分包)app

  • FixedLengthFrameDecoder(使用定長的報文來分包)socket

來看一下這兩種解碼器是如何進行分包的。


DelimiterBasedFrameDecoder分包

先看一個實例,一個netty的example,github地址

https://github.com/netty/netty/tree/master/example/src/main/java/io/netty/example/securechat

只是其中的一個設置編解碼器的類。

package com.usoft.chat;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.ssl.SslContext;

/**
 * Creates a newly configured {@link ChannelPipeline} for a new channel.
 */
public class SecureChatServerInitializer extends
        ChannelInitializer<SocketChannel> {

    private final SslContext sslCtx;

    public SecureChatServerInitializer(SslContext sslCtx) {
        this.sslCtx = sslCtx;
    }

    @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // Add SSL handler first to encrypt and decrypt everything.
        // In this example, we use a bogus certificate in the server side
        // and accept any invalid certificates in the client side.
        // You will need something more complicated to identify both
        // and server in the real world.
        pipeline.addLast(sslCtx.newHandler(ch.alloc()));

        // On top of the SSL handler, add the text line codec.
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters
            .lineDelimiter()));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new StringEncoder());

        // and then business logic.
        pipeline.addLast(new SecureChatServerHandler());
    }
}

其中這行代碼就是設置解碼器來分包的,

 pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));

以下是這個類的屬性字段,

private final ByteBuf[] delimiters;  // 分包的分隔符數組
private final int maxFrameLength; //報文(幀)的最大的長度
private final boolean stripDelimiter;  // 是否除去分隔符(若是數據包中含有分隔符,不影響)
private final boolean failFast; // 爲true是說發現讀到的數據已經超過了maxFrameLength了,當即報TooLongFrameException,若是爲false就是讀完整個幀數據後再報
private boolean discardingTooLongFrame; //是否拋棄超長的幀
private int tooLongFrameLength; //就是說出現了超長幀,那這個幀的長度究竟是多少,就是這個長度,通常來講是在發現當前buffer的可讀數據超過最大幀時候進行設置

其實這個例子都不會形成粘包,由於客戶端的每次輸入而後回車都會使客戶端進行一次writeAndFlush。這裏只不過是演示了 DelimiterBasedFrameDecoder的用法。同時使用這個解碼器也能夠實現分包。


FixedLengthFrameDecoder分包

實例以下,服務器端代碼,

package com.usoft.demo1;

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.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;

/**
 * 定長解碼 服務器端
 * 
 * @author xwalker
 */
public class Server {

    public static void main(String[] args) throws Exception {
        int port = 8000;
        new Server().bind(port);
    }

    public void bind(int port) throws Exception {
        //接收客戶端鏈接用
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //處理網絡讀寫事件
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //配置服務器啓動類
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 100)
                .handler(new LoggingHandler(LogLevel.INFO))//配置日誌輸出
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch)
                            throws Exception {
                        ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//設置定長解碼器 長度設置爲30
                        ch.pipeline().addLast(new StringDecoder());//設置字符串解碼器 自動將報文轉爲字符串
                        ch.pipeline().addLast(new Serverhandler());//處理網絡IO 處理器
                    }
                });
            //綁定端口 等待綁定成功
            ChannelFuture f = b.bind(port).sync();
            //等待服務器退出
            f.channel().closeFuture().sync();
        } finally {
            //釋放線程資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}
package com.usoft.demo1;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

/**
 * 服務器handler
 *
 * @author xwalker
 */
public class Serverhandler extends ChannelHandlerAdapter {
    private static final String MESSAGE = "It greatly simplifies and streamlines network programming such as TCP and UDP socket server.";
    int counter = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("接收客戶端msg:[" + msg + "]");
        ByteBuf echo = Unpooled.copiedBuffer(MESSAGE.getBytes());
        ctx.writeAndFlush(echo);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

}

客戶端代碼,

package com.usoft.demo1;

import io.netty.bootstrap.Bootstrap;
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.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
/**
 * 客戶端 
 * @author xwalker
 *
 */
public class Client {
    /**
     * 連接服務器
     * @param port
     * @param host
     * @throws Exception
     */
    public void connect(int port,String host)throws Exception{
        //網絡事件處理線程組
        EventLoopGroup group=new NioEventLoopGroup();
        try{
        //配置客戶端啓動類
        Bootstrap b=new Bootstrap();
        b.group(group).channel(NioSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)//設置封包 使用一次大數據的寫操做,而不是屢次小數據的寫操做
        .handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new FixedLengthFrameDecoder(30));//設置定長解碼器
                ch.pipeline().addLast(new StringDecoder());//設置字符串解碼器
                ch.pipeline().addLast(new ClientHandler());//設置客戶端網絡IO處理器
            }
        });
        //鏈接服務器 同步等待成功
        ChannelFuture f=b.connect(host,port).sync();
        //同步等待客戶端通道關閉
        f.channel().closeFuture().sync();
        }finally{
            //釋放線程組資源
            group.shutdownGracefully();
        }
    }
    public static void main(String[] args) throws Exception {
        int port=8000;
        new Client().connect(port, "127.0.0.1");
 
    }
 
}
package com.usoft.demo1;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

/**
 * 客戶端處理器
 * 
 * @author xwalker
 */
public class ClientHandler extends ChannelHandlerAdapter {
    private static final String MESSAGE = "Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients.";

    public ClientHandler() {
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer(MESSAGE.getBytes()));
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
            throws Exception {
        System.out.println("接收服務器響應msg:[" + msg + "]");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

服務器端打印結果:

receive client msg:[Netty is a NIO client server f]
receive client msg:[ramework which enables quick a]
receive client msg:[nd easy development of network]
receive client msg:[ applications such as protocol]

客戶端打印結果:

receive sever msg:[It greatly simplifies and stre]
receive sever msg:[amlines network programming su]
receive sever msg:[ch as TCP and UDP socket serve]
receive sever msg:[r.It greatly simplifies and st]
receive sever msg:[reamlines network programming ]
receive sever msg:[such as TCP and UDP socket ser]
receive sever msg:[ver.It greatly simplifies and ]
receive sever msg:[streamlines network programmin]
receive sever msg:[g such as TCP and UDP socket s]
receive sever msg:[erver.It greatly simplifies an]
receive sever msg:[d streamlines network programm]
receive sever msg:[ing such as TCP and UDP socket]

客戶端須要和服務器端約定每一個包的大小爲定長的,這樣服務器端才能夠根據這個規則來分包。這裏也是演示了FixedLengthFrameDecoder解碼器的用法。

參考和引用:

http://my.oschina.net/imhoodoo/blog/357290

http://asialee.iteye.com/blog/1783842

============END============

相關文章
相關標籤/搜索