netty ArrayIndexOutOfBoundsException: 1

318
14 февраля 2017, 18:32

Добрый день! Я новичок в программировании, осваиваю java и netty 4. Пишу небольшое клиент-серверное приложение с использованием netty. Суть простая - раз в 5 секунд формируется выборка из базы данных турникета по проходам людей. Если в этой выборке есть человек, который нам нужен, клиенту отсылается соответствующее сообщение. Если в выборке нужного человека не оказалось, отправляем "nothing", что служит заодно пингом для проверки, что соединение активно. То есть сервер отправляет клиенту сообщения каждые 5 секунд, на что клиент отправляет серверу логин текущего пользователя. И так по кругу. Однако случается так, что клиент ловит ArrayIndexOutOfBoundsException: 1, теряет соединение с сервером. При это по логам видно, что отправленное сервером сообщение было разделено на два сообщения, и на каждое из них отработал channelReadComplete

Сервер:

public class StartServer {
    private static final Logger LOGGER = LogManager.getLogger();
    private int port = Integer.parseInt(ServerFunctions.loadProperties("Port"));
    public StartServer() {
    }
        public void startThatServer() throws Exception {
        LOGGER.info("Запуск сервера...");
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("idleStateHandler",
                                    new IdleStateHandler(8,8,8, TimeUnit.SECONDS));
                            ch.pipeline().addLast(new ServerHandler());                        }
                    })
                    .option(ChannelOption.SO_REUSEADDR, true)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
            ;
            ChannelFuture f = b.bind().sync();
            LOGGER.info(Server.class.getName() + " запущен и слушает порт: " +
                    f.channel().localAddress());
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    } //end of startserver()
}

ServerHandler:

public class ServerHandler extends ChannelInboundHandlerAdapter {
    Watch nothingWatch = new Watch("nothing");
    Message nothingMessage = new Message(nothingWatch);
    private static final Logger LOGGER = LogManager.getLogger();
    private boolean noError = true;
    private List<String> clientTabels = new ArrayList<>(); // получаем список табельных, за которыми наблюдает клиент
    private ArrayList<Message> watchToSend = new ArrayList<>(); // данные, которые будут отправлены клиенту, когда он назовет logonName

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        InetSocketAddress socketAddress = (InetSocketAddress)ctx.channel().remoteAddress();
        InetAddress inetAddress = socketAddress.getAddress();
        String clientHostName = socketAddress.getHostName();
        String clientIpAdress = inetAddress.getHostAddress();
        ByteBuf in = (ByteBuf) msg;
        String logonName = in.toString(CharsetUtil.UTF_8);
        try {
            //получили logonName, делаем немного магии и отправляем нужные данные назад
            clientTabels.clear();
            watchToSend.clear();
            // заносим в clientTabels все табельные номера из WatchAсtual-таблицы
            clientTabels.addAll(ServerFunctions.convertLogonNametoTabelNumbers(dbPostgre, logonName));
            // ищем каждое значение из clientTabels в выборке viborkaForPeriod, если находим - помещаем его в watchToSend
            for (String tabel: clientTabels) {
                for (Watch watch: GenerateViborka.viborkaForPeriod) {
                    if(tabel.equals(watch.getTabel())) {
                        Message result = new Message(watch);
                        watchToSend.add(result);
                    }
                }
            }
            // если отправить нечего, заносим в watchToSend служебное сообщение nothingMessage
            if(watchToSend.size() == 0) {
                watchToSend.add(nothingMessage);
            }
            for(Message m: watchToSend) {
                // отправляем ответ клиенту в виде строки
                ctx.write(Unpooled.copiedBuffer(m.toString(), CharsetUtil.UTF_8));
            }

            ctx.flush();
            try {
                Thread.sleep(Integer.parseInt(ServerFunctions.loadProperties("CheckPeriodDelay"))*1000);
            } catch (InterruptedException e) {
                LOGGER.error("Ошибка во время Thread.sleep в потоке channelRead", e);
            }
        } catch(Exception e){
            noError = false;
            LOGGER.error("ChannelHandlerContext exception", e);
        }
    }

Клиент:

public class Client extends Application {
  public static void main(String[] args) {
    launch();
  }
  @ Override
  public void start(final Stage stage) {
    createBootstrap(new Bootstrap(), loop);
  }
  public Bootstrap createBootstrap(Bootstrap bootstrap, EventLoopGroup eventLoop) {
    if (bootstrap != null) {
      final ClientHandler clientHandler = new ClientHandler(this);
      bootstrap.group(eventLoop);
      bootstrap.channel(NioSocketChannel.class);
      bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
      bootstrap.option(ChannelOption.MAX_MESSAGES_PER_READ, 1);
      bootstrap.handler(new ChannelInitializer < SocketChannel > () {@
        Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
          socketChannel.pipeline().addLast("idleStateHandler",
            new IdleStateHandler(10, 10, 10, TimeUnit.SECONDS));
          socketChannel.pipeline().addLast(clientHandler);
        }
      });
      bootstrap.remoteAddress(ClientAppFunctions.loadProperties("Server"), Integer.parseInt(ClientAppFunctions.loadProperties("Port")));
      bootstrap.connect().addListener(new ClientConnectionListener(this));
    }
    return bootstrap;
  }

ClientHandler:

public class ClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private static final Logger LOGGER = LogManager.getLogger();
    private static boolean isConnected;
    private Client client;
    public ClientHandler(Client client) {
        this.client = client;
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        LOGGER.info("Подключились к " + ctx.channel().remoteAddress());
        isConnected = true;
        ClientAppFunctions.setApplicationTrayIcon(isConnected);
//      отправляем логин пользователя
        ctx.writeAndFlush(Unpooled.copiedBuffer(Client.LOGONNAME, CharsetUtil.UTF_8));
    }
@Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
//        получаем ответ сервера и выводим
        String inputMessage = in.toString(CharsetUtil.UTF_8);
        System.out.print(new SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().getTime()) + " From server: " + inputMessage + "\n");

        ArrayList<String> dataToNotification = new ArrayList<>(); // список для хранения строки из data без времени события
        if(!inputMessage.equals("nothing")) {
            List<String> data = Arrays.asList(inputMessage.split("\\^")); // список для хранения разделенных строк из принятого сообщения
            for(String s: data) {
                Client.globalEventsList.add(ClientAppFunctions.splitStringAndGetFirstPart(s) + ClientAppFunctions.splitStringAndGetSecondPart(s));
                dataToNotification.add(ClientAppFunctions.splitStringAndGetSecondPart(s));
            }
        }
        for(String d: dataToNotification) {
        if(Math.round(Double.parseDouble(ClientAppFunctions.loadProperties("NotificationDuration"))) != 0) {
                new JFXPanel();
                ClientAppFunctions.showMessage("Новое событие", d);
            }
        }
}
@Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        System.out.println("channelReadComplete " + new SimpleDateFormat("HH:mm:ss").format(Calendar.getInstance().getTime()));
        // отправляем логин пользователя
        ctx.writeAndFlush(Unpooled.copiedBuffer(Client.LOGONNAME, CharsetUtil.UTF_8));
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
        if(!(evt instanceof IdleStateEvent)) {
            return;
        }
        IdleStateEvent e = (IdleStateEvent) evt;
        if(e.state() == IdleState.ALL_IDLE) {
            // если с соединением все хорошо, но нет траффика за заданный период
            ctx.close();
        }
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        isConnected = false;
        ClientAppFunctions.setApplicationTrayIcon(isConnected);
        // выводим сообщение о потере соединения
        LOGGER.error("Соединение с сервером " + ctx.channel().remoteAddress() + " отсутствует");
        // при потере соединения - реконнект
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(new Runnable() {
            @Override
            public void run() {
                LOGGER.info("Попытка подключиться...");
                client.createBootstrap(new Bootstrap(), eventLoop);
            }
        }, Long.parseLong(ClientAppFunctions.loadProperties("ReconnectTime")), TimeUnit.SECONDS);
        super.channelInactive(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable aCause) throws Exception {
        if (aCause instanceof IOException) {
            LOGGER.error("IO exception ", aCause);
        }
        else {
            LOGGER.error("other exception " + aCause);
        }
        ctx.close();
    }
}

Пример "разорванного сообщения" (разрыв в 15:24:05):

channelReadComplete 15:23:50
15:23:55 From server: nothing
channelReadComplete 15:23:55
15:24:00 From server: nothing
channelReadComplete 15:24:00
15:24:05 From server: 15:23:58 - $Иванов Иван Иванович вышел 
channelReadComplete 15:24:05
15:24:05 From server: из здания^
15:24:05.339 [nioEventLoopGroup-2-1] ERROR watcher2.client.ClientHandler - java.lang.ArrayIndexOutOfBoundsException: 1
channelReadComplete 15:24:05
15:24:05.352 [nioEventLoopGroup-2-1] ERROR watcher2.client.ClientHandler - Соединение с сервером watchsrv/10.7.1.43:6067 отсутствует
15:24:15.354 [nioEventLoopGroup-2-1] INFO  watcher2.client.ClientHandler - Попытка подключиться...
15:24:15.359 [nioEventLoopGroup-2-1] INFO  watcher2.client.ClientHandler - Подключились к watchsrv/10.7.1.43:6067
15:24:15 From server: 15:24:10 - $Иванов Иван Иванович вышел из здания^
channelReadComplete 15:24:15
15:24:20 From server: 15:24:14 - $Иванов Иван Иванович вошел в здание^
channelReadComplete 15:24:20
15:24:25 From server: 15:24:23 - $Иванов Иван Иванович вошел в здание^
channelReadComplete 15:24:25
15:24:30 From server: 15:24:26 - $Иванов Иван Иванович вышел из здания^
channelReadComplete 15:24:30
READ ALSO
Как отображать временные данные?

Как отображать временные данные?

Я создал сайт вопросов и хочу при ответе пользователя на вопросы сделать клик на варианты radio buttonВыбранный вариант должен загрузиться во временную...

292
отправка запросов в БД mySQL на удаленный сервер(для начинающих)

отправка запросов в БД mySQL на удаленный сервер(для начинающих)

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

366