Netty, получающий событие exceptionCaught (), был уволен, и он достиг хвоста конвейера на TextWebsocketEncoder

Я пытаюсь сделать простой декодирование веб-сокета, а затем кодировать, но я получаю это исключение, когда он передает обработчик TextWebsocketDecoder:

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.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.run(SingleThreadEventExecutor.java:112)
    at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
    at java.lang.Thread.run(Thread.java:745)

у меня есть простой инициализатор, который работает до 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());
        }
}

и это TextWebsocketDecoder и 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));          
    } 

}

2 ответов


за исключением

внутри вашего TextWebSocketFrameHandler, вы звоните ctx.fireChannelRead(msg);, Это передает сообщение вверх по 1 цепочке, однако MessageToMessageDecoder не готовы к этому. Чтобы объяснить эту проблему, мне нужно объяснить, как работает MessageToMessageDecoder.

MessageToMessageDecoder работает, ловя каждое сообщение из восходящего потока и передавая их в свой пользовательский код, ваш пользовательский код обрабатывает работу, а mtmd обрабатывает закрытие переданного ресурса в.

поскольку вы передаете ссылку на другую сторону, вы эффективно закрываете WebSocketFrame несколько раз, вызывая ошибки. MessageToMessageDecoder даже предупреждает вас об этом в javadoc.

для того чтобы разрешить проблему, мы следовать инструкцией в руководстве и делаем наше channelRead следующее:

@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());
}

проблема не отправки назад

внутри ваших комментариев Вы заявили, что код ничего не отправляет спина. Это ожидается, поскольку ваш конвейер потребляет только данные и передает их по цепочке. Чтобы исправить это, потребуется некоторая доработка на вашем конвейере.

  1. нам нужно поменять порядок декодера и кодировщика JSON-webframe:

    pipeline.addLast("textWebsocketDecoder",new TextWebsocketEncoder());
    pipeline.addLast("textWebsocketEncoder",new TextWebsocketDecoder());
    

    это потому, что ваш декодер генерирует выход, который будет отправлять обратно ↑ цепь обработчиков, этот выход не будет виден кодировщиком, если декодер был выше этого. (Декодер нельзя назвать декодер после именования netty)

  2. нам нужно изменить ваш декодер, чтобы отправить сгенерированные данные на самом деле обратно chain цепь вместо ↓ в несуществующую пустоту.

    чтобы внести эти изменения, мы собираемся позволить TextWebSocketDecoder расширения ChannelInboundHandlerAdapter вместо MessageToMessageDecoder<TextWebSocketFrame> поскольку мы обрабатываем сообщения, а не передаем их другому обработчику.

    мы меняем сигнатуру метода декодирования к channelRead(ChannelHandlerContext ctx, Object msg), и добавьте некоторые шаблонные код:

    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();
        }
    }
    
  3. мы адаптируем наш код для передачи результата вверх по трубопроводу, а не вниз:

    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();
        }
    }
    

обратите внимание, что у вас может возникнуть соблазн удалить наш предыдущий код из исключения, но это вызовет неопределенное поведение при запуске под асинхронным характером netty.


вы используете SimpleChannelInboundHandler какие автоматическ-выпуски уловили данные согласно документации.

Итак, когда вы звоните ctx.fireChannelRead(msg); чтобы передать msg другим обработчикам на конвейере, будет выпущена проблема besauce msg.

чтобы исправить это, вы можете использовать ChannelInboundHandlerAdapter или вы можете остановить процесс автоматического освобождения SimpleChannelInboundHandler вызывая правильный конструктор, или вы можете вызвать ReferenceCountUtil.retain(msg); перед стрелять верхним на трубопроводе.

см. документацию SimpleChannelInboundHandler здесь: http://netty.io/4.0/api/io/netty/channel/SimpleChannelInboundHandler.html

и читайте о ссылочных подсчитанных объектах здесь (новая концепция netty 4): http://netty.io/wiki/reference-counted-objects.html