Netty-解码器

Netty 解码器

为什么会有粘包拆包?

对于“套接字”来说,并非是我们调用 writer API 便将报文发送给接收方,而是系统底层根据一定的算法,对消息的大小进行拆解或拼装。这也是发送拆包粘包的最大原因。总体来讲,发生 TCP 粘包、拆包主要是由于下面一些原因:
1.应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
2.应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
3.进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>MSS的时候将发生拆包。
4.接收方法不及时读取套接字缓冲区数据,这将发生粘包。

解决方法

TCP本身是面向流的,作为网络服务器,如何从这源源不断涌来的数据流中拆分出或者合并出有意义的信息呢?通常会有以下一些常用的方法:
1.发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。
2.发送端将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。
3.可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

Netty粘包拆包四大解码器

  • FixedLengthFrameDecoder:定长解码器,按固定长度进行消息的读取;
  • LineBasedFrameDecoder:行解码器,按行(\r\n)进行消息的读取;
  • DelimiterBasedFrameDecoder:分隔符解码器,按照特殊的分隔符作为消息分隔符进行消息的读取;
  • LengthFieldBasedFrameDecoder:自定义长度解码器,通过在消息头中定义消息长度字段来标志消息体的长度,然后根据消息的总长度来读取消息;

解码器使用

FixedLengthFrameDecoder

使用定长解码器 FixedLengthFrameDecoder,服务器(客户端)在接收消息后将自行判断该消息长度是否达到规定的帧数,每一帧一个字节,如果达到才会回调 channelRead 函数。否则会阻塞直到消息的长度达到规定的帧数。从而达到粘包拆包的目的。

package org.skystep.tcpserv;

import 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.string.StringDecoder;

public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //定长处理器 这里设置为8帧 每次接收数据会判断是否达到该长度,如果达到就回调 channelRead 函数;否则阻塞住;
        pipeline.addLast(new FixedLengthFrameDecoder(8));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new ServerHandler());
    }
}

如以上事例代码,如果在服务器的处理链上增加 FixedLengthFrameDecoder 解码器并且设置长度为8字节。那么客户端发送"1234567"长度为7的字符串,服务器不会进行读操作,直到下次服务再发送消息,凑足8个字节。

服务端实现

package org.skystep.tcpserv;

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;

public class Server {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();

        try {
            //用于启动NIO服务
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //通过工厂方法设计模式实例化一个channel
            serverBootstrap.group(bossGroup, wokerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ServerInitializer());
            //绑定服务器,该实例将提供有关IO操作的结果或状态的信息
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            System.out.println("在" + channelFuture.channel().localAddress() + "上开启监听");
            //阻塞操作,closeFuture()开启了一个channel的监听器(这期间channel在进行各项工作),直到链路断开
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            wokerGroup.shutdownGracefully();
        }
    }
}

package org.skystep.tcpserv;

import 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.string.StringDecoder;

public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //定长处理器 这里设置为8帧 每次接收数据会判断是否达到该长度,如果达到就回调 channelRead 函数;否则阻塞住;
        pipeline.addLast(new FixedLengthFrameDecoder(8));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new ServerHandler());
    }
}
package org.skystep.tcpserv;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //读取消息,并按照自定义的协议格式进行消息的处理
        String in = (String) msg;
        System.out.println("收到的原始报文: " + in);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //出现异常的时候执行的动作(打印并关闭通道)
        cause.printStackTrace();
        ctx.close();
    }
}

客户端实现

package org.skystep.tcpclient;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}


package org.skystep.tcpclient;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class ClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new ClientHandler());
    }
}

package org.skystep.tcpclient;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.time.LocalDateTime;

import static java.lang.Thread.sleep;

public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //服务端的远程地址
        System.out.println(ctx.channel().remoteAddress());
        System.out.println("client output: "+msg);
        ctx.writeAndFlush("from client: "+ LocalDateTime.now());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 在这里循环发送 1234567890,查看服务器接收数据结果
        for (int i = 0; i < 10000; i++) {
            sleep(1000);
            ctx.writeAndFlush("1234567");
        }

    }

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

LineBasedFrameDecoder

行解码器:使用换行符\n或者\r\n作为依据,遇到\n或者\r\n都认为是一条完整的消息。行解码器提供两个构造函数:
LineBasedFrameDecoder(int maxLength)
LineBasedFrameDecoder(int maxLength,boolean stripDelimiter,boolean failFast)
对应的三个参数解释:

  • maxLength: 表示一行最大的长度,如果超过这个长度依然没有检测到\n或者\r\n,抛出异常;
  • stripDelimiter: 解码后的消息是否去除\n,\r\n分隔符,true:去除换行符,false:原样输出;
  • failFast: 与maxLength联合使用,表示超过maxLength后,抛出TooLongFrameException的时机;
  • true:超出maxLength后立即抛出异常,结束解码,false:等次消息包收完后,再抛出异常。

服务端实现

package org.skystep.linebased.tcpserv;

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;

public class Server {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();

        try {
            //用于启动NIO服务
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //通过工厂方法设计模式实例化一个channel
            serverBootstrap.group(bossGroup, wokerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ServerInitializer());
            //绑定服务器,该实例将提供有关IO操作的结果或状态的信息
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            System.out.println("在" + channelFuture.channel().localAddress() + "上开启监听");
            //阻塞操作,closeFuture()开启了一个channel的监听器(这期间channel在进行各项工作),直到链路断开
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            wokerGroup.shutdownGracefully();
        }
    }
}

package org.skystep.linebased.tcpserv;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 设置长度为128,解码后的消息是否去除 \n,\r\n 分隔符,超出maxLength后立即抛出异常
        pipeline.addLast(new LineBasedFrameDecoder(128, true, true));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new ServerHandler());
    }
}
package org.skystep.linebased.tcpserv;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //读取消息,并按照自定义的协议格式进行消息的处理
        String in = (String) msg;
        System.out.println("收到的原始报文: " + in);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //出现异常的时候执行的动作(打印并关闭通道)
        cause.printStackTrace();
        ctx.close();
    }
}

客户端实现

package org.skystep.linebased.tcpclient;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}
package org.skystep.linebased.tcpclient;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class ClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new ClientHandler());
    }
}
package org.skystep.linebased.tcpclient;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.time.LocalDateTime;

import static java.lang.Thread.sleep;

public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //服务端的远程地址
        System.out.println(ctx.channel().remoteAddress());
        System.out.println("client output: "+msg);
        ctx.writeAndFlush("from client: "+ LocalDateTime.now());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 在这里循环发送 1234567,查看服务器会根据 \r\n 来分段获取
        for (int i = 0; i < 10000; i++) {
            sleep(1000);
            ctx.writeAndFlush("1234567"+"\r\n"+"1234");
        }
    }

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

DelimiterBasedFrameDecoder

DelimiterBasedFrameDecoder 与 LineBasedFrameDecoder 类似,只不过更加通用,允许我们指定任意特殊字符作为分隔符。分隔符解码器可以同时指定多个分隔符,如果指定多个分隔符,则会选择内容最短的一个分隔符作为依据
分隔符解码器有多个构造函数,区别在于以下四个参数的组合:

  • maxFrameLength: 消息的最大长度,超过最大长度抛出异常;
  • stripDelimiter: 解码后的消息是否去除分隔符,true:去除换行符,false:原样输出;
  • failFast: 与maxFrameLength联合使用,表示超过maxFrameLength后,抛出异常的时机,true:超出maxLength后立即抛出异常,结束解码,false:等消息包收完后,再抛出异常;
  • delimiters: 分隔符,这个分隔符的类型要以ByteBuf的形式出现。

服务端实现

package org.skystep.delimiterbased.tcpserv;

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;

public class Server {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();

        try {
            //用于启动NIO服务
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //通过工厂方法设计模式实例化一个channel
            serverBootstrap.group(bossGroup, wokerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ServerInitializer());
            //绑定服务器,该实例将提供有关IO操作的结果或状态的信息
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            System.out.println("在" + channelFuture.channel().localAddress() + "上开启监听");
            //阻塞操作,closeFuture()开启了一个channel的监听器(这期间channel在进行各项工作),直到链路断开
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            wokerGroup.shutdownGracefully();
        }
    }
}
package org.skystep.delimiterbased.tcpserv;

import io.netty.buffer.Unpooled;
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.string.StringDecoder;

public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // 使用$来切割消息,只有遇到该符号才会回调 channelRead 函数,如果接收的消息长度达到128直接抛出异常
        pipeline.addLast(new DelimiterBasedFrameDecoder(128, true,
                true, Unpooled.copiedBuffer("$".getBytes())));
        pipeline.addLast(new StringDecoder());
        pipeline.addLast(new ServerHandler());
    }
}
package org.skystep.delimiterbased.tcpserv;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //读取消息,并按照自定义的协议格式进行消息的处理
        String in = (String) msg;
        System.out.println("收到的原始报文: " + in);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //出现异常的时候执行的动作(打印并关闭通道)
        cause.printStackTrace();
        ctx.close();
    }
}

客户端实现

package org.skystep.delimiterbased.tcpclient;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}
package org.skystep.delimiterbased.tcpclient;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class ClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new ClientHandler());
    }
}
package org.skystep.delimiterbased.tcpclient;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.time.LocalDateTime;

import static java.lang.Thread.sleep;

public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //服务端的远程地址
        System.out.println(ctx.channel().remoteAddress());
        System.out.println("client output: "+msg);
        ctx.writeAndFlush("from client: "+ LocalDateTime.now());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 在这里循环发送 1234567890,查看服务器接收数据结果
        for (int i = 0; i < 10000; i++) {
            sleep(1000);
            ctx.writeAndFlush("12$3456$7");
        }

    }

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

LengthFieldBasedFrameDecoder

自定义长度解码器能够适应各种复杂的通信协议格式,所以应用是最多的。该解码器是四个解码器中相对来说稍显复杂一点,不是实现复杂,是因为以下两个方面的原因:一是参数较多,二是准确计算各参数的值。自定义长度解码器有多个构造函数,这些构造函数是7个参数的不同组合:

  • maxFrameLength: 消息的最大长度,超过最大长度抛出异常;
  • lengthFieldOffset: 长度域偏移量,指的是长度域位于整个数据包字节数组中的下标;
  • lengthFieldLength: 长度域自己的字节数长度;
  • lengthAdjustment: 默认值0,长度域的偏移量矫正,长度域的长度值是否要减去其它长度才是内容的值;
  • initialBytesToStrip: 默认值0,丢弃的起始字节数;
  • failFast: 默认值true,超过消息最大长度是否立即抛出异常;
  • ByteOrder: 字节序列模式,默认是大端模式,也可以设置为小端模式。

例如服务和客户端约定的协议格式如下:

对应的函数参数可以拆解为:

服务端实现

package org.skystep.lengthfieldbased.tcpserv;

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;

public class Server {
    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();

        try {
            //用于启动NIO服务
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //通过工厂方法设计模式实例化一个channel
            serverBootstrap.group(bossGroup, wokerGroup).channel(NioServerSocketChannel.class)
                    .childHandler(new ServerInitializer());
            //绑定服务器,该实例将提供有关IO操作的结果或状态的信息
            ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
            System.out.println("在" + channelFuture.channel().localAddress() + "上开启监听");
            //阻塞操作,closeFuture()开启了一个channel的监听器(这期间channel在进行各项工作),直到链路断开
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            bossGroup.shutdownGracefully();
            wokerGroup.shutdownGracefully();
        }
    }
}

package org.skystep.lengthfieldbased.tcpserv;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 3, 4, 0, 0));
        pipeline.addLast(new ServerHandler());
    }
}
package org.skystep.lengthfieldbased.tcpserv;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //读取消息,并按照自定义的协议格式进行消息的处理
        System.out.println("####################################################################333");
        ByteBuf in = (ByteBuf) msg;
        String srcInfo = ByteBufUtil.hexDump(in).toUpperCase();
        System.out.println("收到的原始报文: " + srcInfo);
        short header = in.readShort();
        System.out.println("消息头:" + header);
        byte msgType = in.readByte();
        System.out.println("消息类型:" + msgType);
        int contentLen = in.readInt();
        System.out.println("消息长度:" + contentLen);
        ByteBuf bufContent = in.readBytes(contentLen);
        String strContent = bufContent.toString(CharsetUtil.UTF_8);
        System.out.println("消息内容为:" + strContent);
        System.out.println("####################################################################333");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //出现异常的时候执行的动作(打印并关闭通道)
        cause.printStackTrace();
        ctx.close();
    }
}

客户端实现

package org.skystep.lengthfieldbased.tcpclient;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {
    public static void main(String[] args) {
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());

            ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            eventLoopGroup.shutdownGracefully();
        }
    }
}

package org.skystep.lengthfieldbased.tcpclient;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class ClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();

        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new ClientHandler());
    }
}

package org.skystep.lengthfieldbased.tcpclient;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;

import java.time.LocalDateTime;

import static java.lang.Thread.sleep;

public class ClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //服务端的远程地址
        System.out.println(ctx.channel().remoteAddress());
        System.out.println("client output: "+msg);
        ctx.writeAndFlush("from client: "+ LocalDateTime.now());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 在这里循环发送 1234567,查看服务器会根据 \r\n 来分段获取
        for (int i = 0; i < 10000; i++) {
            sleep(1000);
            short header = 0x5A5A;
            byte[] nsgType = new byte[]{(byte)0x01};
            String strContent="测试发送消息" + i;
            int len = strContent.getBytes(CharsetUtil.UTF_8).length;
            ctx.write(Unpooled.copyShort(header));
            ctx.write(Unpooled.copiedBuffer(nsgType));
            ctx.write(Unpooled.copyInt(len));
            ctx.write(Unpooled.copiedBuffer(strContent, CharsetUtil.UTF_8));
            ctx.flush();
        }
    }

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

参考说明

TCP粘包,拆包及解决方法

posted @ 2021-12-30 00:55  yaomianwei  阅读(13)  评论(0编辑  收藏  举报