I currently have set up a networked client that makes use of two encoders, a single decoder, and a ChannelInboundMessageHandlerAdapter<ByteBuf>. The encoders are a MessageToByteEncoder<Packet> and a ByteToByteEncoder. I've tried using a ChannelOutboundMessageHandlerAdapter and a ChannelOutboundByteHandlerAdapter but neither of them remedied the problem. It enters the first encoder (PacketEncoder or the MessageToByteEncoder<Packet>) just fine, but fails to enter the ByteToByteEncoder afterword and no data is sent to the server.
My pipeline is set up like so:
ChannelPipeline pipeline = ch.pipeline();
// Decoders
pipeline.addLast("buffer_length_decoder", new BufferLengthDecoder());
pipeline.addLast("packet_decoder", new PacketDecoder());
// Encoder
pipeline.addLast("buffer_length_encoder", new BufferLengthEncoder());
pipeline.addLast("packet_encoder", new PacketEncoder());
PacketEncoder looks like so:
public class PacketEncoder extends MessageToByteEncoder<Packet> {
private static final Logger logger = LoggerFactory.getLogger(PacketEncoder.class);
#Override
protected void encode(ChannelHandlerContext ctx, Packet msg, ByteBuf out) throws Exception {
ByteBuf buf = msg.buf();
buf = buf.capacity(buf.readableBytes());
logger.info(Utils.toHexString(buf.array()));
out.writeBytes(buf);
}
}
BufferLengthEncoder looks like so:
public class BufferLengthEncoder extends ByteToByteEncoder {
private static final Logger logger = LoggerFactory.getLogger(BufferLengthEncoder.class);
#Override
protected void encode(ChannelHandlerContext ctx, ByteBuf in, ByteBuf out) throws Exception {
logger.info(Utils.toHexString(in.array()));
out = out.writeInt(in.readableBytes()).writeBytes(in);
}
}
I have tried changing their order in the pipeline to no avail. I assume that I'm just missing something nonsensical somewhere, but I'm unsure where or what. Any and all help is much appreciated.
Thanks in advance!
Sounds strange to me... could you please write a unit test that shows the problem and open an issue after you are sure it is a bug ?
https://github.com/netty/netty/issues
Related
I try to do simple web socket decode and then encode but I'm getting this exception when it pass the TextWebsocketDecoder handler:
io.netty.channel.DefaultChannelPipeline$TailContext exceptionCaught
WARNING: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
at io.netty.buffer.AbstractReferenceCountedByteBuf.release(AbstractReferenceCountedByteBuf.java:101)
at io.netty.buffer.DefaultByteBufHolder.release(DefaultByteBufHolder.java:73)
at io.netty.util.ReferenceCountUtil.release(ReferenceCountUtil.java:59)
at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:112)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
at io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler$1.channelRead(WebSocketServerProtocolHandler.java:147)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:86)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:276)
at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:263)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:318)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:304)
at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:131)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:511)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:112)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
at java.lang.Thread.run(Thread.java:745)
What I have is simple Initializer which work find until TextWebsocketEncoder:
public class ServerInitializer extends ChannelInitializer<Channel> {
private final ChannelGroup group;
public GameServerInitializer(ChannelGroup group) {
this.group = group;
}
#Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
pipeline.addLast(new ChunkedWriteHandler());
pipeline.addLast(new HttpRequestHandler("/ws"));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new TextWebSocketFrameHandler(group));
pipeline.addLast("textWebsocketDecoder",new TextWebsocketDecoder());
pipeline.addLast("textWebsocketEncoder",new TextWebsocketEncoder());
}
}
TextWebSocketFrameHandler
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
private final ChannelGroup group;
public TextWebSocketFrameHandler(ChannelGroup group) {
this.group = group;
}
#Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
ctx.pipeline().remove(HttpRequestHandler.class);
group.writeAndFlush(new TextWebSocketFrame("Client " + ctx.channel() + " joined"));
group.add(ctx.channel());
} else {
super.userEventTriggered(ctx, evt);
}
}
#Override
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
ctx.fireChannelRead(msg);
//group.writeAndFlush(msg.retain());
}
}
and this are the TextWebsocketDecoder and TextWebsocketEncoder :
TextWebsocketDecoder :
public class TextWebsocketDecoder extends MessageToMessageDecoder<TextWebSocketFrame>
{
#Override
protected void decode(ChannelHandlerContext ctx, TextWebSocketFrame frame, List<Object> out) throws Exception
{
String json = frame.text();
JSONObject jsonObject = new JSONObject(json);
int type = jsonObject.getInt("type");
JSONArray msgJsonArray = jsonObject.getJSONArray("msg");
String user = msgJsonArray.getString(0);
String pass = msgJsonArray.getString(1);
String connectionkey = msgJsonArray.getString(2);
int timestamp = jsonObject.getInt("timestamp");
JSONObject responseJson = new JSONObject();
responseJson.put("type",Config.LOGIN_SUCCESS);
responseJson.put("connectionkey",connectionkey);
out.add(responseJson); // After This im getting the exception !!!
}
}
TextWebsocketEncoder
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
public class TextWebsocketEncoder extends MessageToMessageEncoder<JSONObject>
{
#Override
protected void encode(ChannelHandlerContext arg0, JSONObject arg1, List<Object> out) throws Exception {
String json = arg1.toString();
out.add(new TextWebSocketFrame(json));
}
}
The exception
Inside your TextWebSocketFrameHandler, you are calling ctx.fireChannelRead(msg);, this passes the message up 1 chain, however MessageToMessageDecoder isn't prepared to deal with this. To explain this problem I need to explain how the MessageToMessageDecoder works.
MessageToMessageDecoder works by catching every message from the upstream and passing them to your custom code, your custom code handles the work, and the mtmd handles the closing of the resource you passed in.
Since you are passing the reference to the other side, you are effectively closing the WebSocketFrame multiple times, causing bugs. MessageToMessageDecoder even warns you for this in the javadoc.
To solve the problem, we follow the instruction in the manual and make our channelRead the following:
#Override
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
msg.retain(); // ferrybig: fixed bug http://stackoverflow.com/q/34634750/1542723
ctx.fireChannelRead(msg);
//group.writeAndFlush(msg.retain());
}
The not sending back problem
Inside your comments, you stated the code doesn't send anything back. This is expected as your pipeline only consumes data and passes it up the chain. To fix this, it would require some rework at your pipeline.
We need to swap the order of the json-webframe decoder and encoder:
pipeline.addLast("textWebsocketDecoder",new TextWebsocketEncoder());
pipeline.addLast("textWebsocketEncoder",new TextWebsocketDecoder());
This is because your Decoder is generating the output that would be send back ↑ the chain of handlers, this output won't be seen by the encoder if the decoder was above that. (Your decoder shouldn't be called a decoder following the netty naming)
We need to change your decoder to send the generated data actually back ↑ the chain instead of ↓ into the non-existing void.
To make these changes, we going to let the TextWebSocketDecoder extend ChannelInboundHandlerAdapter instead of MessageToMessageDecoder<TextWebSocketFrame> since we are handling messages instead of passing them to a other handler.
We are changing the signature of the decode method to channelRead(ChannelHandlerContext ctx, Object msg), and add some boilerplate code:
public void channelRead(ChannelHandlerContext ctx, Object msg) /* throws Exception */
TextWebSocketFrame frame = (TextWebSocketFrame) msg;
try {
/* Remaining code, follow the steps further of see end result */
} finally {
frame.release();
}
}
We adapt our code to pass the result up the pipeline instead of down:
public void channelRead(ChannelHandlerContext ctx, Object msg) /* throws Exception */
TextWebSocketFrame frame = (TextWebSocketFrame) msg;
try {
String json = frame.text();
JSONObject jsonObject = new JSONObject(json);
int type = jsonObject.getInt("type");
JSONArray msgJsonArray = jsonObject.getJSONArray("msg");
String user = msgJsonArray.getString(0);
String pass = msgJsonArray.getString(1);
String connectionkey = msgJsonArray.getString(2);
int timestamp = jsonObject.getInt("timestamp");
JSONObject responseJson = new JSONObject();
responseJson.put("type",Config.LOGIN_SUCCESS);
responseJson.put("connectionkey",connectionkey);
ctx.writeAndFlush(responseJson)
} finally {
frame.release();
}
}
Notice that you may be tempted to remove our previous code from the exception, but doing this will trigger undefined behavior when ran under the async nature of netty.
You use SimpleChannelInboundHandler which auto-releases catched data according to documentation.
So, when you call ctx.fireChannelRead(msg); to pass msg to others handlers on pipeline, there is a problem besauce msg will be released.
To fix this, you can use ChannelInboundHandlerAdapter or you can stop auto-releasing process of SimpleChannelInboundHandler by calling the proper constructor, or you can call ReferenceCountUtil.retain(msg); before firing upper on pipeline.
See documentation of SimpleChannelInboundHandler here:
http://netty.io/4.0/api/io/netty/channel/SimpleChannelInboundHandler.html
and read about Reference counted objects here (new concept of netty 4):
http://netty.io/wiki/reference-counted-objects.html
I'm new in Netty, and I decided to start with 4.0.0, because I thought it should be better, because it's newer. My server application should receive data from gps devices, and the process is like this - at first I'm receiving 2 bytes, which are length of device imei, and then I'm receiving imei with that length, then I should send 0x01 to device if I want to accept data from it. After my answer device sends me gps data with AVL protocol. Now my server is working without Netty, and I want to change it to work with netty.
This is what I have done:
I have created server class like this
public class BusDataReceiverServer {
private final int port;
private final Logger LOG = LoggerFactory.getLogger(BusDataReceiverServer.class);
public BusDataReceiverServer(int port) {
this.port = port;
}
public void run() throws Exception {
LOG.info("running thread");
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new BusDataReceiverInitializer());
b.bind(port).sync().channel().closeFuture().sync();
}catch (Exception ex){
LOG.info(ex.getMessage());
}
finally {
LOG.info("thread closed");
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new BusDataReceiverServer(3129).run();
}
}
and created initializer class
public class BusDataReceiverInitializer extends ChannelInitializer<SocketChannel> {
#Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast("imeiDecoder", new ImeiDecoder());
pipeline.addLast("busDataDecoder", new BusDataDecoder());
pipeline.addLast("encoder", new ResponceEncoder());
pipeline.addLast("imeiHandler", new ImeiReceiverServerHandler());
pipeline.addLast("busDataHandler", new BusDataReceiverServerHandler());
}
}
then I have created decoders and encoder and 2 handlers. My imeiDecoder and encoder, and ImeiReceiverServerHandler are working. This is my ImeiReceiverServerHandler
public class ImeiReceiverServerHandler extends ChannelInboundHandlerAdapter {
private final Logger LOG = LoggerFactory.getLogger(ImeiReceiverServerHandler.class);
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageList<Object> msgs) throws Exception {
MessageList<String> imeis = msgs.cast();
String imei = imeis.get(0);
ctx.write(Constants.BUS_DATA_ACCEPT);
ctx.fireMessageReceived(msgs);
}
#Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx); //To change body of overridden methods use File | Settings | File Templates.
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause); //To change body of overridden methods use File | Settings | File Templates.
}
}
Now, after accepting I don't understand how to continue receive gps data and forward it to handler BusDataReceiverServerHandler.
If anyone could help me with this or could offer me useful documentation, I will be very grateful. Or if it is possible to do this with Netty 3, for this I will also be thankful.
I have not used Netty 4, so I am not sure if my answer will be 100% accurate or the best way to do things in Netty 4, but what you need to do is track the state of your connection / client session in order to know when to forward messages to your second handler.
E.g.
private enum HandlerState { INITIAL, IMEI_RECEIVED; }
private HandlerState state = HandlerState.INITIAL;
#Override
public void messageReceived(ChannelHandlerContext ctx, MessageList<Object> msgs) throws Exception
{
if (state == HandlerState.INITIAL)
{
MessageList<String> imeis = msgs.cast();
String imei = imeis.get(0);
ctx.write(Constants.BUS_DATA_ACCEPT);
state = HandlerState.IMEI_RECEIVED;
} else
{
// Forward message to next handler...
// Not sure exactly how this is done in Netty 4
// Maybe: ctx.fireMessageReceived(msgs);
// Or maybe it is:
// ctx.nextInboundMessageBuffer().add(msg);
// ctx.fireInboundBufferUpdated();
// I believe you could also remove the IMEI handler from the
// pipeline instead of having it keep state, if it is not going to do anything
// further.
}
}
So either track state in the handler, or remove the handler from the pipeline once it has finished if it will not be used further. When tracking state, you can either keep the state in the handler itself (as shown above), or keep the state variables in the context / attribute map (however that is done in netty 4).
The reason to not keep the state in the handler itself would be if you were going to make the handler shareable (one instance used across multiple channels). It is not necessary to do this, but there could be some resource savings if you have a large number of concurrent channels.
I'm using netty 4.0.0-CR3, following the example on server-side:
https://github.com/netty/netty/blob/master/example/src/main/java/io/netty/example/telnet/TelnetServerPipelineFactory.java
I've constructed my pipeline as follows:
private static final StringDecoder DECODER = new StringDecoder(CharsetUtil.UTF_8);
#Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", DECODER);
// and then business logic
pipeline.addLast("serverHandler", new ServerHandler());
}
And handler:
public class ServerHandler extends ChannelInboundMessageHandlerAdapter<String> {
private static final Logger LOGGER = LoggerFactory.getLogger(ServerHandler.class);
public void messageReceived(ChannelHandlerContext ctx, String request)
throws Exception {
// Displays the message
LOGGER.info("Received: " + request);
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
LOGGER.error("Unexpected exception from downstream.", cause);
ctx.close();
}
}
I created a simple C# client that encodes String into bytes, and send to the server. However, I don't see EITHER StringDecoder's decode() OR handler's messageReceived() called.
I then removed StringDecoder() in pipeline, and changed the handler to be:
public class Handler extends ChannelInboundByteHandlerAdapter {
#Override
protected void inboundBufferUpdated(ChannelHandlerContext ctx, ByteBuf in)
throws Exception {
System.out.println("called " + in.toString(CharsetUtil.UTF_8));
}
}
Now it works properly. Functionally both pipelines should work right? Why is the first setup not working? The client code is the same.
Thanks a lot!
The documentation for StringDecoder clearly states that it must be used in conjunction with a ByteToMessageDecoder if used over a stream connection (such as TCP). The example you refer to has such a handler in front of the StringDecoder.
Thanks guys! so I added the following:
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.nulDelimiter()));
And this still didn't work until I explicitly add '\0' to the end to String in C# :
ASCIIEncoding encoder = new ASCIIEncoding();
int index = random.Next(0, 2);
byte[] buffer = encoder.GetBytes(list[index] + "\0");
The weird thing is that, I was using Netty 3.6 previously, and everything worked fine even without a FrameDecoder (only StringDecoder was there / client code was same) but now I have to do the steps above to make it to work..........?
I am using Netty 4 RC1. I initialize my pipeline at the client side:
public class NodeClientInitializer extends ChannelInitializer<SocketChannel> {
#Override
protected void initChannel(SocketChannel sc) throws Exception {
// Frame encoding and decoding
sc.pipeline()
.addLast("logger", new LoggingHandler(LogLevel.DEBUG))
// Business logic
.addLast("handler", new NodeClientHandler());
}
}
NodeClientHandler has the following relevant code:
public class NodeClientHandler extends ChannelInboundByteHandlerAdapter {
private void sendInitialInformation(ChannelHandlerContext c) {
c.write(0x05);
}
#Override
public void channelActive(ChannelHandlerContext c) throws Exception {
sendInitialInformation(c);
}
}
I connect to the server using:
public void connect(final InetSocketAddress addr) {
Bootstrap bootstrap = new Bootstrap();
ChannelFuture cf = null;
try {
// set up the pipeline
bootstrap.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new NodeClientInitializer());
// connect
bootstrap.remoteAddress(addr);
cf = bootstrap.connect();
cf.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture op) throws Exception {
logger.info("Connect to {}", addr.toString());
}
});
cf.channel().closeFuture().syncUninterruptibly();
} finally {
bootstrap.shutdown();
}
}
So, what I basically want to do is to send some initial information from the client to the server, after the channel is active (i.e. the connect was successful). However, when doing the c.write() I get the following warning and no package is send:
WARNING: Discarded 1 outbound message(s) that reached at the head of the pipeline. Please check your pipeline configuration.
I know there is no outbound handler in my pipeline, but I didn't think I need one (at this point) and I thought Netty would take care to transport the ByteBuffer over to the server. What am I doing wrong here in the pipeline configuration?
Netty only handle messages of type ByteBuf by default if you write to the Channel. So you need to wrap it in a ByteBuf. See also the Unpooled class with its static helpers to create ByteBuf instances.
I am trying to implement async file upload with progress with sonatype async http client - https://github.com/sonatype/async-http-client.
I tried the method suggested in the docs. Using transfer listener.
http://sonatype.github.com/async-http-client/transfer-listener.html
I implemented onBytesSent of TransferListener interface (just as test):
public void onBytesSent(ByteBuffer byteBuffer) {
System.out.println("Total bytes sent - ");
System.out.println(byteBuffer.capacity());
}
Then in another thread(because I don't want to block the app) I tried to do the following:
TransferCompletionHandler tl = new TransferCompletionHandler();
tl.addTransferListener(listener);
asyncHttpClient.preparePut(getFullUrl(fileWithPath))
.setBody(new BodyGenerator() {
public Body createBody() throws IOException {
return new FileBodyWithOffset(file, offset);
}
})
.addHeader(CONTENT_RANGE, new ContentRange(offset, localSize).toString())
.execute(handler).get();
Everything is fine. File is uploaded correctly and very fast. But the issue is - I am getting messages from onBytesSent in TransferListener only AFTER the upload is finished. For exmaple the upload is completed in 10 minutes. And during that 10 minutes I get nothing. And only after that everything is printed on the console.
I can't figure out what is wrong with this code. I just tried to follow the docs.
I tried to execute the above code in the main thread and it didn't work either.
Maybe it is a wrong way to implement upload progress listener using this client?
I will answer it myself. I did not manage to resolve the issue with TransferListener. So I tried the other way.
I had put the progress logick inside Body interface implementation (inside read method):
public class FileBodyWithOffset implements Body {
private final ReadableByteChannel channel;
private long actualOffset;
private final long contentLength;
public FileBodyWithOffset(final File file, final long offset) throws IOException {
final InputStream stream = new FileInputStream(file);
this.actualOffset = stream.skip(offset);
this.contentLength = file.length() - offset;
this.channel = Channels.newChannel(stream);
}
public long getContentLength() {
return this.contentLength;
}
public long read(ByteBuffer byteBuffer) throws IOException {
System.out.println(new Date());
actualOffset += byteBuffer.capacity();
return channel.read(byteBuffer);
}
public void close() throws IOException {
channel.close();
}
public long getActualOffset() {
return actualOffset;
}
}
Maybe it is a dirty trick, but at least it works.