netty解析自定义长度的tcp报文--java处理tcp接口数据

1.理解netty的decode处理过程,2.netty的Bytebuf在decode时的处理过程,两个指针

netty是tcp包–>decode整理报文–>业务handler的过程。decode如果list.add()但是没有读取报文,报DecoderException说你decode但是没有读取任何报文,这是netty为了防止自己开发decode出现bug

ByteBuf.readBytes(0)会返回一个空bytebuf导致
list.add(ByteBuf.readBytes(0)) 
报错decode() did not read anything but decoded a message

要理解netty的Bytebuf,有两个指针readerIndex,writerIndex来记录你的decode的读与netty的报文写入,可以看看这个
一起学Netty(五)之 初识ByteBuf和ByteBuf的常用API

decode中list.add(msg)把你decode整理的一个完整报文放入list给业务handler处理。由于异步,会先调用多次decode,然后是多次的业务handler(MyHandler), MyHandler中的channelRead的Object msg就是你out.add()加入的一个报文

如果报文中有其他冗余的信息,可以在decode中设置ByteBuf的readerIndex去略过这个信息,或者可以设置一个waste的 byte数组,让ByteBuf.reads…(waste),但是waste不加入list,就是空读,只读不用,来清除冗余信息,也可以在list.add()前,加入一些信息到报文中

Test.java 提供模拟tcp接口数据的源,可以自己定义byte[]或者保存的tcp接口的报文写入的文件

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.*;

public class Test
{

    public static void main(String[] args) throws Exception
    {
        ServerSocket serverSocket = new ServerSocket(10086);


        while (true){
            Socket socket = serverSocket.accept();
            System.out.println("accept");

//                byte[] resp = ("a@$@bbbbb@$@cccccccccc@$@ddddddddddddddddddddddddddddddddddddddd@$@33333333333@$@4              v@$@").getBytes();
//                InputStream inputStream = new FileInputStream(new File("D:/proj/netty/tcpradio.dat"));
                InputStream inputStream = new FileInputStream(new File("tcpradio.dat"));
                OutputStream outputStream = socket.getOutputStream();
                byte[] readBuf = new byte[10240];
                int readLen;
                readLen= inputStream.read(readBuf,0,10240);
                System.out.println(readLen);
                outputStream.write(readBuf,0,readLen);
                inputStream.close();
                outputStream.close();
            }
    }

}


TcpInterface.java netty的启动类

package com.tgram.api2Mq;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
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.DelimiterBasedFrameDecoder;



public class TcpInterface {

    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 MyDecodeHandler());
                            ch.pipeline().addLast(new MyHandler());
                        }
                    });
            ChannelFuture f = b.connect(host,port).sync();

            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        TcpInterface tcpInterface = new TcpInterface();
        tcpInterface.connect(10086,"127.0.0.1");
    }
}

MyDecodeHandler.java 自定义解码拼装成一个完整报文(就是我们定义的一个完整的信息报文,不是tcp的底层一个报文,tcp的报文是分片,是一串字节流)


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

import java.util.List;

public class MyDecodeHandler  extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        //取到tag与报文长度后再处理,各2字节
        if(byteBuf.readableBytes()<4){
            return;
        }
        //记录当前ByteBuf的读指针位置,以便下面取报文长度字节
        //pos是一个完整报文的开始位置,报文整体会在ByteBuf中移动,类似内存管理,所以基于字节的判断报文长度等等,都是基于pos,否则可以在byteBuf.readBytes()之后加,byteBuf.discardReadBytes();整理ByteBuf,使pos回到0开始位置
        int pos = byteBuf.readerIndex();
        int msgLen = ((byteBuf.getByte(pos +3) &0xFF)<<8) | (byteBuf.getByte(pos+2) &0xFF);

        //收到的报文长度不足一个完整的报文,继续接收
        if(byteBuf.readableBytes()<msgLen){
            return;
        }

        //提出完整报文(readBytes读到msg中),放到list给下一个Handler处理
        if(msgLen>0){
            list.add(msg);
        }
     }
}

MyHandler.java 正式处理解码后的报文


import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;

public class MyHandler extends ChannelInboundHandlerAdapter
{
    private int count = 0;

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
    {
        ByteBuf byteBuf = (ByteBuf) msg;
        // 判断报文长度
        int bufLen = byteBuf.readableBytes();
        //当前处理的是第几个字节开始的报文
        System.out.println("pos:" + count);
        //统计已处理的所有报文字节长度
        count += bufLen;

        System.out.println("msg:" + bufLen);

        //业务的报文处理

        //必须释放,如果继承simplechannelinboundhandler会自动释放,但是报文处理写在channelRead0
        ReferenceCountUtil.release(msg)
    }
}
  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Netty解析multipart/form-data请求体中的二进制数据需要进行以下步骤: 1. 创建一个自定义的ChannelHandler来处理HTTP请求。你可以扩展Netty的`SimpleChannelInboundHandler`类,并重写`channelRead0`方法。 2. 在`channelRead0`方法中,检查请求是否是multipart/form-data类型。你可以通过检查请求头的Content-Type来判断。如果是multipart/form-data类型的请求,你需要使用Netty提供的`HttpPostRequestDecoder`来解码请求体。 3. 创建一个新的`HttpPostRequestDecoder`对象,并将HTTP请求传递给它。然后,使用`isMultipart`方法检查请求是否是一个有效的multipart请求。 4. 使用`offer`方法将所有的HTTP请求内容添加到`HttpPostRequestDecoder`中,直到请求被完全解码。 5. 使用`next()`方法从解码器中获取每个解码后的HTTP内容。通常,这将返回一个`InterfaceHttpData`对象,你可以根据其类型进行处理。 6. 如果`InterfaceHttpData`是`FileUpload`类型,那么这个对象就代表一个上传的文件。你可以使用`FileUpload`对象的方法来获取文件名、保存文件等。 7. 如果`InterfaceHttpData`是`Attribute`类型,那么这个对象就代表一个表单字段。你可以使用`Attribute`对象的方法来获取字段名和字段值。 下面是一个示例代码片段,展示了如何在Netty解析multipart/form-data请求体中的二进制数据: ```java import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.FileUpload; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; public class MultipartHandler extends SimpleChannelInboundHandler<FullHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { if (request.method() == HttpMethod.POST && HttpHeaders.is100ContinueExpected(request)) { ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("HTTP/1.1 100 Continue\r\n\r\n".getBytes())); } if (request.method() == HttpMethod.POST && request.headers().contains(HttpHeaders.Names.CONTENT_TYPE)) { String contentType = request.headers().get(HttpHeaders.Names.CONTENT_TYPE); if (contentType.startsWith("multipart/form-data")) { HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request); while (decoder.hasNext()) { InterfaceHttpData data = decoder.next(); if (data != null) { if (data instanceof FileUpload) { FileUpload fileUpload = (FileUpload) data; // 处理上传的文件 String fileName = fileUpload.getFilename(); // 保存文件到磁盘等操作 } else if (data instanceof Attribute) { Attribute attribute = (Attribute) data; // 处理表单字段 String fieldName = attribute.getName(); String fieldValue = attribute.getValue(); } data.release(); } } } } } } ``` 这只是一个简单的示例,你可以根据你的需要进行调整和扩展。请注意,这个示例中使用的是Netty 4.x版本的API,如果你使用的是其他版本,可能会有一些差异。确保你根据你使用的Netty版本进行相应的调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值