Netty TCPServer无法使用解码器从通道读取确切的帧

时间:2018-12-10 17:27:27

标签: java sockets netty nio tcpserver

下面是我的TCPServer代码

public class TCPServer {

private final int port;

public TCPServer(final int port) {
    this.port = port;
}

public void run() throws Exception {
    final ExecutorService threadPool = Executors.newCachedThreadPool();
    final EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
    final EventLoopGroup workerGroup = new NioEventLoopGroup(50, threadPool);
    try {
        final ServerBootstrap b = new ServerBootstrap(); // (2)

        b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) // (3)
                .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                    @Override
                    public void initChannel(final SocketChannel ch) throws Exception {
                         //ch.pipeline().addLast(new ServerRequestHandler());
                        ch.pipeline().addLast(new ReqMessageDecoder(1024, 0, 2, 0, 2), new ServerRequestHandler());
                    }
                }).option(ChannelOption.SO_BACKLOG, 1000) // (5)
                .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)

        // Bind and start to accept incoming connections.
        final ChannelFuture f = b.bind(this.port).sync(); // (7) // Start
                                                          // the server

        // Wait until the server socket is closed.
        // In this example, this does not happen, but you can do that to
        // gracefully
        // shut down your server.
        f.channel().closeFuture().sync();
    } finally {
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
        threadPool.shutdown();
    }
}

public static void main(final String[] args) throws Exception {
    int port = 9090;
    if (args.length > 0) {
        port = Integer.parseInt(args[0]);
    }

    new TCPServer(port).run();
}
}

MyRequestHandler类

public class ServerRequestHandler extends ChannelInboundHandlerAdapter {


@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) { // (2)

    log(" ServerRequestHandler reading message :: " + Thread.currentThread().getName());

    ctx.write(msg);

}
}

我的解码器类

public class ReqMessageDecoder extends LengthFieldBasedFrameDecoder {

public ReqMessageDecoder(final int maxFrameLength, final int lengthFieldOffset, final int lengthFieldLength,
            final int lengthAdjustment, final int initialBytesToStrip) {
        super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
    }

}

下面有一个示例TCP客户端代码,该代码以循环方式将示例消息循环发送到上述服务器79次。

public static void main(final String args[]) {
        int i = 80;
        final String message = "This is a sample message but we can send actual";
        final TestClass testClass = new TestClass();
        testClass.startConnection("localhost", 9090);
        while (i > 1) {
            i--;
            testClass.sendMessageLength(testClass.getMessageLength(message).getBytes());
            testClass.sendMessage(message);

        }
        testClass.stopConnection();
    }

private Socket clientSocket;
private PrintWriter out;
private BufferedReader in;
private OutputStream outputStream;

public String getMessageLength(final String message) {
    final int firstByte = message.length() >> 8;
    final int secondByte = message.length() - (firstByte << 8);
    System.out.println("char 0 " + (char) firstByte + " char 1 " + (char) secondByte);
    final String str = new String(new char[] { (char) firstByte, (char) secondByte });
    System.out.println("firstByte :: " + firstByte + " secondByte :: " + secondByte + "Str :: " + str);
    return str;
}

public void startConnection(final String ip, final int port) {
    try {
        this.clientSocket = new Socket(ip, port);
this.outputStream = this.clientSocket.getOutputStream();
        this.out = new PrintWriter(this.outputStream, true);
        this.in = new BufferedReader(new InputStreamReader(this.clientSocket.getInputStream()));
    } catch (final UnknownHostException e) {
        e.printStackTrace();
    } catch (final IOException e) {
        e.printStackTrace();
    }

}

public String sendMessage(final String msg) {
    this.out.println(msg);
    String resp = null;
    try {
        resp = this.in.readLine();
        System.out.println("resp :: " + resp);
    } catch (final IOException e) {
        e.printStackTrace();
    }
    return resp;
}

 public void sendMessageLength(final byte[] msg) {
    try {
        this.outputStream.write(msg);
    } catch (final IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }

}

在我的客户端中,我首先在套接字输出流上以2个字节的形式发送消息的长度(请参见getMessageLength),然后是实际的消息(请参见sendMessage)。现在,当服务器收到消息时,它将在下面打印。

输出:

    channel Active
 ServerRequestHandler reading message :: pool-1-thread-1 message recieved :: This is a sample message but we can send actualmessage ended
exception caught
io.netty.handler.codec.TooLongFrameException: Adjusted frame length exceeds 1024: 2562 - discarded
    at io.netty.handler.codec.LengthFieldBasedFrameDecoder.fail(LengthFieldBasedFrameDecoder.java:522)
    at io.netty.handler.codec.LengthFieldBasedFrameDecoder.failIfNecessary(LengthFieldBasedFrameDecoder.java:500)
    at io.netty.handler.codec.LengthFieldBasedFrameDecoder.exceededFrameLength(LengthFieldBasedFrameDecoder.java:387)
    at io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:430)
    at io.netty.handler.codec.LengthFieldBasedFrameDecoder.decode(LengthFieldBasedFrameDecoder.java:343)
    at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:502)
    at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:441)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:278)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:656)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:591)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:508)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:470)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:909)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
channel read complete
channel inactive

在我的代码中,我没有发送大于50个字节的数据,但不确定为什么TCPserver无法使帧大小大于1024。我怀疑长度计算无法按预期进行。

我是Netty的新手,也没有其他问题。

  1. channelRead方法是否针对收到的每条消息在单独的线程中调用?
  2. 是否在阻塞线程中调用解码方法,在该线程中它以阻塞方式从TCP / IP流中读取(n)个字节并将控件传递给requestHandler以进行channelRead
  3. 如何仅接受使用服务器端安全套接字创建的连接?
  4. 我想在并发线程中处理来自单个客户端的消息以增加服务器的吞吐量...我是否还需要做其他事情(channelRead方法内的生成线程?)或NioEventLoopGroup支持开箱即用吗?

2 个答案:

答案 0 :(得分:0)

ch.pipeline().addLast(new ReqMessageDecoder(1024, 0, 2, 0, 2), new ServerRequestHandler());

您的ReqMessageDecoder期望帧的最大长度为1024,超过该长度将导致异常。您如何定义框架边界?帧边界将告诉您的解码器一个完整的帧已被接收,并且可以由您的应用程序代码处理,否则解码器将继续读取输入的数据,直到达到最大帧大小(例如1024)或帧边界字符/序列为止收到。

请参阅以下链接,了解netty内置的基于行尾的解码器,您可以将其添加到频道DelimiterBasedFrameDecoder.

答案 1 :(得分:0)

因此,经过大量的尝试和错误并通过不同的链接,我已经解决了这个问题,而我的PoC并不完整。

new ReqLengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 2, -2, 2) 
// This will read 1st 2 bytes as length and will skip those to read the next 
// bytes (i.e. length mentioned in 1st 2 bytes)

是否为每个收到的消息在单独的线程中调用channelRead方法?

回答:为此,您需要执行以下操作。

pipeline.addLast(this.threadPool, new ServerRequestHandler());
// this.threadPool refers to UnorderedThreadPoolEventExecutor 
// ServerRequestHandler extends SimpleChannelInboundHandler<T>

请参见Netty 4. Parallel processing after ByteToMessageCodec

解码方法是否在阻塞线程中调用,该线程以阻塞方式从TCP / IP流中读取(n)个字节并将控件传递给requestHandler以进行channelRead?

回答:是的,但是这些线程与I / O线程不同。这些线程在通道管道上工作。

如何仅接受使用服务器端安全套接字创建的连接?

答案:请参见下面的基本设置

// some dummy SSL setup. actual production code will have some more
// sophisticated steps.
final SelfSignedCertificate ssc = new SelfSignedCertificate();
SslContext sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build();
pipeline.addLast(sslCtx.newHandler(ch.alloc()));

我想在并发线程中处理来自单个客户端的消息,以增加服务器的吞吐量...我是否还需要做其他任何事情(在channelRead方法内生成线程?),或者NioEventLoopGroup支持此功能?盒子?

回答:请参阅Netty multi threading per connectionNetty 4. Parallel processing after ByteToMessageCodec