netty自定义封包实现

1 篇文章 0 订阅

说明

netty是java重要的企业级NIO,使用它可以快速实现很多功能通信功能如:http、ftp、socket、websocket、udp等。
本站使用自定义网包实现网络通信。

分享

内置编码器和解码器

解码器

名称说明
ByteToMessageDecoder如果想实现自己的半包解码器,实现该类
MessageToMessageDecoder一般作为二次解码器,当我们在 ByteToMessageDecoder 将一个 bytes 数组转换成一个 java 对象的时候,我们可能还需要将这个对象进行二次解码成其他对象,我们就可以继承这个类;
LineBasedFrameDecoder通过在包尾添加回车换行符 \r\n 来区分整包消息;
StringDecoder字符串解码器;
DelimiterBasedFrameDecoder特殊字符作为分隔符来区分整包消息;
FixedLengthFrameDecoder报文大小固定长度,不够空格补全;
ProtoBufVarint32FrameDecoder通过 Protobuf 解码器来区分整包消息;
ProtobufDecoderProtobuf 解码器;
LengthFieldBasedFrameDecoder指定长度来标识整包消息,通过在包头指定整包长度来约定包长。

编码器

名称说明
ProtobufEncoderProtobuf 编码器;
MessageToByteEncoder将 Java 对象编码成 ByteBuf;
MessageToMessageEncoder如果不想将 Java 对象编码成 ByteBuf,而是自定义类就继承这个;
LengthFieldPrependerLengthFieldPrepender 是一个非常实用的工具类,如果我们在发送消息的时候采用的是:消息长度字段+原始消息的形式,那么我们就可以使用 LengthFieldPrepender。这是因为 LengthFieldPrepender 可以将待发送消息的长度(二进制字节长度)写到 ByteBuf 的前两个字节。

代码实现

创建核心类

消息实体类

public class MyMessage {
    private int len;//发送内容的长度
    private byte[] content;//发送的内容
 
 
    public int getLen() {
        return len;
    }
 
    public void setLen(int len) {
        this.len = len;
    }
 
    public byte[] getContent() {
        return content;
    }
 
    public void setContent(byte[] content) {
        this.content = content;
    }
}

自定义编码类

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

public class MyMessageEncoder extends MessageToByteEncoder<MyMessage> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, MyMessage myMessage, ByteBuf byteBuf) throws Exception {
        byteBuf.writeInt(myMessage.getLen());
        byteBuf.writeBytes(myMessage.getContent());
    }


}

自定义解码类

import java.util.List;

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

public class MyMessageDecoder extends ByteToMessageDecoder {
    int length=0;
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        //将二进制字节码转为对象
        if(byteBuf.readableBytes()>=4){
            if(length==0){
                length=byteBuf.readInt();
            }
            if(byteBuf.readableBytes()<length){
                // System.out.println("可读数据不够,继续等待");
                return;
            }
            byte[] content=new byte[length];
            byteBuf.readBytes(content);
            MyMessage message=new MyMessage();
            message.setLen(length);
            message.setContent(content);
            list.add(message);//传递给下一个handler
            length=0;
        }
    }

}

服务端

ServerHandler

import com.netty.cn.rpc.selfmessage.core.MyMessage;

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

public class MyServerHandler extends SimpleChannelInboundHandler<MyMessage> {
    private int count;
    /**
     * 读取客户端的数据
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MyMessage myMessage) throws Exception {
        System.out.println("服务端收到消息:");
        System.out.println("长度:"+myMessage.getLen());
        System.out.println("内容: "+new String(myMessage.getContent(),CharsetUtil.UTF_8));
        System.out.println("收到消息数量:"+(++count));
        
        String msg="服务端收到请求";
        MyMessage message=new MyMessage();
        message.setContent(msg.getBytes(CharsetUtil.UTF_8));
        message.setLen(msg.getBytes(CharsetUtil.UTF_8).length);
        ctx.writeAndFlush(message);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
    	super.channelReadComplete(ctx);
    	// 客户端连接进入 FIN_WAIT1 状态
    	//    ctx.channel().close();
    }
}

入口类

import com.netty.cn.rpc.selfmessage.core.MyMessageDecoder;
import com.netty.cn.rpc.selfmessage.core.MyMessageEncoder;

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

public class MyServer {
    public static void main(String[] args)  {
    	
    	int port=8080;
    	
        EventLoopGroup bossGroup=new NioEventLoopGroup(1);//处理连接请求
        EventLoopGroup workerGroup=new NioEventLoopGroup();//默认线程数量为cpu核数的两倍,处理业务
        try {
            ServerBootstrap bootstrap=new ServerBootstrap();//创建服务器端的启动对象
            bootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,port)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) {
                            ChannelPipeline channelPipeline=socketChannel.pipeline();
                            channelPipeline.addLast(new MyMessageDecoder());//加解码器
                            channelPipeline.addLast(new MyMessageEncoder());
                            channelPipeline.addLast(new MyServerHandler());
                        }
                    });
            System.out.println("netty server start");
            //启动服务器绑定端口,bind是异步操作,sync是等待
            ChannelFuture cf=bootstrap.bind(port).sync();
 
            cf.channel().closeFuture().sync();
        }catch(Exception e){
//        	log.error(e.toString(),e);
        	System.out.println(e.toString());
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

客户端

ClientHandler

import com.netty.cn.rpc.selfmessage.core.MyMessage;

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

public class MyClientHandler extends SimpleChannelInboundHandler<MyMessage> {
 
 
    /**
     * 当客户端连接到服务端是触发
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    	System.out.println("连接服务端 "+ctx.channel().remoteAddress()+" 成功");
    	
        String msg="你好,我是张asdfasdfsadfwerwerwerwerewrewrewrewr三。";
        for (int i=0;i<20;i++){
            MyMessage message=new MyMessage();
            message.setContent(msg.getBytes(CharsetUtil.UTF_8));
            message.setLen(msg.getBytes(CharsetUtil.UTF_8).length);
            ctx.writeAndFlush(message);
        }
    }
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, MyMessage myMessage) throws Exception {
    	System.out.println("client 接收到信息:"+new String(myMessage.getContent()).toString());
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

}

入口类

import com.netty.cn.rpc.selfmessage.core.MyMessageDecoder;
import com.netty.cn.rpc.selfmessage.core.MyMessageEncoder;

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

public class MyClient {
    public static void main(String[] args) {
    	int port=8080;
    	
        EventLoopGroup group=new NioEventLoopGroup();
        try {
            Bootstrap bootstrap=new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) {
                            ChannelPipeline channelPipeline=socketChannel.pipeline();
                            channelPipeline.addLast(new MyMessageDecoder());//加解码器
                            channelPipeline.addLast(new MyMessageEncoder());
                            channelPipeline.addLast(new MyClientHandler());
                        }
                    });
            //System.out.println("netty client start");
            //启动客户端连接服务器
            ChannelFuture cf =bootstrap.connect("127.0.0.1",port).sync();
            //关闭通道进行监听
            cf.channel().closeFuture().sync();
            System.out.println("启动客户端"+port);
        } catch(Exception e){
//        	log.error(e.toString(),e);
        	System.out.println(e.toString());
        }finally {
            group.shutdownGracefully();
        }
    }
}

测试

  • 先启动 MyServer,再启动 MyClient,可以看到控制台打印如下内容:
  • Server
netty server start
服务端收到消息:
长度:60
内容: 你好,我是张asdfasdfsadfwerwerwerwerewrewrewrewr三。
收到消息数量:1
  • Client
连接服务端 /127.0.0.1:8080 成功
client 接收到信息:服务端收到请求

参考

总结

  • 该方式定义了数据传输结构,传输过程中由编码器ByteBuf 完成数据处理。
  • 由于内容是二进制格式,可以存储数据,如json字符串、protobuf二次处理后数据,提升了数据传输灵活性。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Netty中,自定义拆包可以通过实现`ByteToMessageDecoder`来实现。`ByteToMessageDecoder`是Netty中的一个抽象类,它提供了一个decode方法,可以将接收到的字节转换成对象。 具体实现步骤如下: 1. 继承`ByteToMessageDecoder`,实现decode方法。 ```java public class MyDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { // TODO:实现自定义拆包逻辑 } } ``` 2. 在decode方法中实现自定义拆包逻辑。例如,我们可以通过读取字节流中的前4个字节来获取消息的长度,再根据长度读取消息内容。 ```java public class MyDecoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { // 如果可读字节数小于4,说明数据还不足,返回等待更多数据 if (in.readableBytes() < 4) { return; } // 标记读取位置 in.markReaderIndex(); // 读取消息长度 int length = in.readInt(); // 如果可读字节数小于消息长度,说明数据还不足,重置读取位置并返回等待更多数据 if (in.readableBytes() < length) { in.resetReaderIndex(); return; } // 读取消息内容 byte[] data = new byte[length]; in.readBytes(data); // 将消息内容添加到输出列表中 out.add(new String(data, StandardCharsets.UTF_8)); } } ``` 3. 将自定义拆包器添加到Netty的ChannelPipeline中。 ```java public class MyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // 添加自定义拆包器 pipeline.addLast(new MyDecoder()); // 添加自定义业务逻辑处理器 pipeline.addLast(new MyServerHandler()); } } ``` 以上就是在Netty实现自定义拆包的步骤。需要注意的是,在自定义拆包器中,需要考虑粘包的情况,即当一个TCP包中包含多个消息时,需要将其拆分成多个消息进行处理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值