Please give me advice how to increase my ByteBuf initial capacity. In situation like:
#Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
byte[] byteinput = new byte[in.readableBytes()];
in.readBytes(byteinput);
//further handling...
}
If income message more than max capacity of ByteBuf - i get cutted data. Its vital for this project to get whole, non chunked message.
I suppose i need to set initial capacity of ByteBuf somewhere in bootstraps childOptions, or in cannel.config()... inside of ChannelInitializer.
And i tried different ways like setting
ch.config().setReceiveBufferSize(1024)
but i still have same value of ByteBuf capacity(e.g. 496).
UPD
I discovered my protocol traffic with wireshark, and packets up to 1,4k going uncorrupted out and in from my test user client. This issue is only matter of netty settings. Operating system socket buffer do not cuts messages.
That was easy as pie.
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
#Override
public void initChannel(SocketChannel ch) throws Exception {
//decrypt //checknum
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048)); //set buf size here
ch.pipeline().addLast(new InboundDecryptor());
.
.
.
You may be able to configure Netty's buffer allocation sizes but there are likely more general limitations you are subject to. Netty is an asynchronous framework. This means it will read what ever is made available to it by the OS and pass that on to you. Netty has no control over network conditions, networking hardware behavior, OS behavior, or anything else in between your producer of data and your Netty application. If your application logic requires complete application level messages you may have to aggregate the data before you invoke this application logic. Netty has some convenience methods to help with this see MessageAggregator.java and for an HTTP specific implementation see HttpObjectAggregator.java.
Scott is right. Increasing only the size of buffer does not solve the problem.
ch.config().setRecvByteBufAllocator(new FixedRecvByteBufAllocator(2048));
For HTTP requests it also should use HTTPObjectAggregator. It worked for me.
Related
We are migrating from Servlet API 2.5 (thread per request) in our application to Netty. One of cases is using blocking-style ini file parser which we had for many years. Current approach is accumulating incoming ByteBuf into CompositeByteBuf and feeding it to a parser after wrapping with ByteBufInputStream.
In real application we are using HTTP and ini is sent to a server as HTTP request body. But in snippet below it is assumed that all inbound content is transferred file.
class AccumulatingChannelHandler extends ChannelInboundHandlerAdapter {
final BlockingIniFileParser parser = new BlockingIniFileParser();
CompositeByteBuf accumulator;
#Override
public void channelActive(ChannelHandlerContext ctx) {
accumulator = ctx.alloc().compositeBuffer(Integer.MAX_VALUE);
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf ioBuffer = (ByteBuf) msg;
accumulator.addComponent(true, ioBuffer);
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
IniFile iniFile = parser.parse(new ByteBufInputStream(accumulator));
accumulator.release();
ByteBuf result = process(iniFile);
ctx.writeAndFlush(result);
ctx.close();
}
private ByteBuf process(IniFile iniFile) {...}
}
class BlockingIniFileParser {
IniFile parse(InputStream in) {...}
}
interface IniFile {
String getSetting(String section, String entry);
}
By default pooled direct buffers are coming to channelRead method. And with such strategy we risk to get uncontrollable consumption of direct memory. So, I would like to understand:
Is it rational to accumulate IO buffers in such fashion?
Is there any best practice for integrating Netty IO with blocking parsers?
What is the best practice for parsing input (in some structured format) with Netty?
You have not mentioned the size of a typical uploaded ini file, but assuming they're large enough to cause some concern, I would consider ditching the CompositeByteBuf. Allocate an un-pooled [optionally direct] buffer and as pooled buffers come in the door, write them to the un-pooled buffer, then release them. When you're done reading, continue with your use of a ByteBufInputStream around the un-pooled buffer. Once complete, the un-pooled buffer will be GCed.
You will still be allocating chunks of memory, but it won't be drawn from your buffer pools, and if you use a direct un-pooled buffer, it will not impact your heap as much.
Ultimately, if the size of the un-pooled buffer remains a concern, I would bite the bullet and write the incoming pooled buffers out to disk and on completion, read them back in using a FileInputStream.
When trying to write with netty, the written data never ends up at the remote side, confirmed with Wireshark.
I have tried:
//Directly using writeAndFlush
channel.writeAndFlush(new Packet());
//Manually flushing
channel.write(new Packet());
channel.flush();
// Even sending bytes won't work:
channel.writeAndFlush(new byte[]{1,2,3});
No exception is caught when I wrap it in try{...}catch(Throwable e){e.printStackTrace();}
What can I do to debug this problem?
Netty is asynchronous, meaning that it won't throw exceptions when a write failed. Instead of throwing exceptions, it returns a Future<?> that will be updated when the request is done. Make sure to log any exceptions coming from this as your first debugging steps:
channel.writeAndFlush(...).addListener(new GenericFutureListener<Future<Object>>() {
#Override
public void operationComplete(Future<Object> future) {
// TODO: Use proper logger in production here
if (future.isSuccess()) {
System.out.println("Data written succesfully");
} else {
System.out.println("Data failed to write:");
future.cause().printStackTrace();
}
}
});
Or more simply:
channel.writeAndFlush(...).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
After you get the root cause of the exception, there could be multiple problems:
java.lang.UnsupportedOperationException:unsupported message type: <type> (expected: ...)
Notice: This also throws when using an ObjectEncoder, but your object does not implements Serializable
A default Netty channel can only send ByteBufs and FileRegions. You need to convert your objects to these types either by adding more handlers to the pipeline, or converting them manually to ByteBufs.
A ByteBuf is the Netty variant of a byte array, but has the potential for performance because it can be stored in the direct memory space.
The following handlers are commonly used:
To convert a String use a StringEncoder
To convert a Serializable use a ObjectEncoder (warning, not compatible with normal Java object streams)
To convert a byte[] use a ByteArrayEncoder
Notice: Since TCP is a stream based protocol, you usually want some form of packet sizes attached, since you may not receive exact packets that you write. See Dealing with a Stream-based Transport in the Netty wiki for more information.
I have the following Server and Client initializers (both have extremely similar code where only sch changes to cch for the client, both representing their respective handlers).
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("handler", sch);
ch.pipeline().addLast(new CommonClassHandler());
ch.pipeline().addLast("frameDecoder",
new ProtobufVarint32FrameDecoder());
ch.pipeline().addLast("protobufDecoder",
new ProtobufDecoder(Server.MyMessage.getDefaultInstance()));
ch.pipeline().addLast("frameEncoder", new ProtobufVarint32LengthFieldPrepender());
ch.pipeline().addLast("protobufEncoder", new ProtobufEncoder());
}
I wish to use a binary format when sending commands/actions to the client or the server, therefore, I'm using Google's Protocol Buffers.
Here is where I create a builder when dealing with the client's input:
while (channel.isOpen()) {
Client.MyMessage.Builder builder = Client.MyMessage.newBuilder();
String input = in.readLine(); // Save console input
builder.setKeyword(input); // Set the value of keyword to said input
channel.writeAndFlush(builder.build()); // Send the build to the server
}
And finally here is the method automatically called when the server / client receive a message:
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf bb = (ByteBuf)msg;
String order = bb.toString(Charset.defaultCharset());
System.out.println(order); // Displays received data
Server.MyMessage.Builder builder = Server.MyMessage.newBuilder();
builder.setKeyword("301");
ctx.writeAndFlush(builder.build());
}
1) When displaying the contents of my ByteBuf it displays two unknown characters and a "\n" prior to my message; maybe I should handle my received data another way to have a normal display?
2) After displaying the received data, my server should send the answer "301" to my client, but to no use, as my client does not display anything (the method is not even called in the Client handler), is there an apparent reason?
Please excuse my questions, but there is very little documentation concerning the use of Protocol Buffers with Netty 4.1.6.
You are adding your handler at the start of your pipeline ch.pipeline().addLast("handler", sch);, but you should be putting it at the end of the pipeline, after your protobufDecoder.
Once you make that change you should start receiving MyMessage as your msg instead of a ByteBuf. I'm guessing that the unknown characters you are seeing right now are the frame lengths that get stripped by the frame decoder you have, but it won't run until after your handler the way you have things setup right now.
Why doesn't channelRead() give me the full message I send to the server? Fragmentation sometimes occur when messages are getting above 140 bytes (Roughly, sometimes more and sometimes less). I'm using a TCP socket using the NioServerSocketChannel class.
I'm using 4.1.0.Beta5.
Isn't there a way to read the full message when it has arrived?
this.serverBootstrap = new ServerBootstrap();
this.serverBootstrap.group(new NioEventLoopGroup(1), new NioEventLoopGroup(6))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>()
{
#Override
public void initChannel(SocketChannel ch) throws Exception
{
ch.pipeline().addLast(new TestServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, (int)Short.MAX_VALUE)
.option(ChannelOption.SO_RCVBUF, (int) Short.MAX_VALUE)
.option(ChannelOption.SO_KEEPALIVE, true)
.option(ChannelOption.TCP_NODELAY, true);
this.serverBootstrap.bind(this.host, this.port);
And class TestServerHandler extends ChannelInboundHandlerAdapter:
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String s = buffer.toString(CharsetUtil.UTF_8);
for(int i = 0; i < 20; i++)
{
s = s.replace("[" + ((char)i) + "]", i + "");
}
System.out.println(s.length() + "");
System.out.println();
System.out.println();
System.out.println(s);
}
I need a way to get the full bytebuf / bytearray when it has fully arrived at the server and get notified of that so my application can respond in a correct way according to the data the client has send.
So in short: How can I prevent fragmentation and have the channelRead event output the whole message / bytebuf.
The basic data type used by Netty is Channel Buffers or ByteBuf. This is simply a collection of bytes and nothing else. In your code you have simply used a custom handler to handle the raw incoming data. This is generally not a good practice. A very basic netty pipeline should look something like the following
So a pipeline consists of a decoder / encoder and then we have our custom handlers or logging handlers. We never really handle any raw data as is. TCP is a stream protocol. It does not identify when a specific packet ends and a new packet starts. Even if we send a very very large packet or say two individual packets, they will simply be treated as a set of bytes and when we try to read the raw set of bytes, fragmentation might happen.
So properly implement a channel pipeline which consists of a String decoder / encoder (whatever you need) and this problem will go away.
TCP provides a stream of bytes, so you can't rely on receiving a complete message in one packet. You will need a handler in your pipeline that knows how your messages are framed. Netty provides some built-in handlers that you can adapt for your protocol. See Dealing with a Stream-based Transport in the Netty User Guide.
I know that a server sends MP3 stream after connecting to it and sending few bytes. How to read it's transmission with Apache MINA? Can you provide any examples please?
You need a client to read data from server. If it is possible to make a TCP connection with the server you can get help from this tutorial on Apache MINA TCP client
[UPDATE]
Data will be received in ClientSessionHandler's messageReceived. You can override this function according to you need. You may go through SumUp example to understand it fully.
[UPDATE 2]
To receive bytes in your case, you will have to update messageReceived of your session handler a bit. You can use IoBuffer to read byte. Something like this :
public void messageReceived(IoSession session, Object message) {
if (message instanceof IoBuffer) {
IoBuffer buffer = (IoBuffer) message;
byte[] b = new byte[buffer.remaining()];
buffer.get(b);
}
}