title: Netty server stops receiving packets after a short client inactivity time
body: <p>I'm making a TCP client-server project and I am having some problems with it. When I connect a client to the server, it works perfectly while the client is active and sending some packets (packets sent from swing triggered events). If I let my app sit for about 10-15 minutes, then try to use it again, the client says it sent the packets but the server doesn't receive them. I even completely shut off the client and the server still says it's connected. I really need to have the client be able to stay idle while the user is inactive and still be able to work in the future. There are no errors on either side and I've tried everything I could find, SO_KEEPALIVE, IdleStateHandler, ReadTimeoutHandler, etc. The server still stops receiving packets after some inactivity.</p>
  
  <p>Here's the main code for client/server.</p>
  
  <p>Thanks</p>
  
  <p>Server:</p>
  
  <pre><code>public class DataServer {
  
      private final int port;
  
      private final Lock packetLock = new ReentrantLock();
      private final Lock trafficLock = new ReentrantLock();
      private final PacketManager packetManager = new PacketManager();
      private final ExecutorService thread = Executors.newSingleThreadExecutor(); 
      private final Map&lt;String, ClientListener&gt; clients = new ConcurrentHashMap&lt;String, ClientListener&gt;();
  
      protected final RequestPool requestPool = new RequestPool();
      protected final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
  
      private boolean running;
      private ServerFuture serverFuture;
      private ClientEventHandler eventHandler;
      private EventLoopGroup bossGroup, workerGroup;
  
      public DataServer(int port) {
          this(port, null);
      }
  
      public DataServer(int port, ClientEventHandler eventHandler) {
          this.port = port;
          this.eventHandler = eventHandler;
      }
  
      private void configureBootstrap() {
          bossGroup = new NioEventLoopGroup();
          workerGroup = new NioEventLoopGroup();
          try {
              new ServerBootstrap()
              .group(bossGroup, workerGroup)
              .channel(NioServerSocketChannel.class)
              .option(ChannelOption.TCP_NODELAY, true)
              .option(ChannelOption.SO_KEEPALIVE, true)
              .childHandler(new ChannelInitializer&lt;SocketChannel&gt;() {
                  @Override
                  public void initChannel(SocketChannel ch) throws Exception {
                      ch.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(1, TimeUnit.HOURS));
                      ch.pipeline().addLast("frameDecoder", new Varint21FrameDecoder());
                      ch.pipeline().addLast("decoder", new PacketDecoder(packetManager));
                      ch.pipeline().addLast("framePrepender", new Varint21LengthFieldPrepender());
                      ch.pipeline().addLast("encoder", new PacketEncoder(packetManager));
                      ch.pipeline().addLast(new ServerHandler(DataServer.this));
                  }
              })
              .bind(port).addListener(new ChannelFutureListener() {
                  public void operationComplete(ChannelFuture future) throws Exception {
                      if (serverFuture != null) {
                          if (future.isSuccess()) {           
                              serverFuture.startupSuccess();
                              } else {
                              serverFuture.startupFailed();
                          }   
                      }
                  }
              }).sync().channel().closeFuture().sync();
          } catch (InterruptedException e) {
              e.printStackTrace();
          } finally {
              bossGroup.shutdownGracefully();
              workerGroup.shutdownGracefully();
          }
      }
  
      public void start() {
          start(null);
      }
  
      public void start(ServerFuture future) {
          if (running) {
              throw new DataException("Server has already been started");
          }
          serverFuture = future;
          running = true;
          thread.execute(new Runnable() {
              public void run() {
                  configureBootstrap();
              }
          });
      }
  
      public void stop() {
          if (!running) {
              throw new DataException("Server hasn't been bound to a port yet");
          }
          channelGroup.close();
          bossGroup.shutdownGracefully();
          workerGroup.shutdownGracefully();
          thread.shutdownNow();
          requestPool.clear();
          running = false;
      }
  
      public void sendAll(Packet packet) {
          channelGroup.writeAndFlush(packet);
      }
  
      public boolean hasClient(String id) {
          return clients.containsKey(id);
      }
  
      public ClientListener getClient(String id) {
          return clients.get(id);
      }
  
      public ClientListener getClient(Channel channel) {
          return clients.get(channel.id().asShortText());
      }
  
      public Set&lt;ClientListener&gt; activeClients() {
          return new HashSet&lt;ClientListener&gt;(clients.values());
      }
  
      public int activeClientCount() {
          return clients.size();
      }
  
      public PacketManager packetManager() {
          return packetManager;
      }
  
      public int getPort() {
          return port;
      }
  
      protected void fireClientConnected(Channel channel) {
          if (eventHandler == null) return;
          try {
              trafficLock.lock();
              String id = channel.id().asShortText();
              if (clients.containsKey(id)) {
                  throw new DataException("Cannot register duplicate client: " + id);
              }
              ClientListener client = new ClientListener(channel, this); 
              client.send(new HandshakePacket(client.getId()));
              clients.put(id, client);
              if (eventHandler != null) {
                  eventHandler.clientConnected(client);
              }   
          } finally {
              trafficLock.unlock();
          }
      }
  
      protected void fireClientDisconnected(Channel channel) {
          if (eventHandler == null) return;
          try {
              trafficLock.lock();
              ClientListener client = getClient(channel);
              if (clients.containsKey(client.getId())) {
                  clients.remove(client.getId());
                  if (eventHandler != null) {
                      eventHandler.clientDisconnected(client);
                  }
              }  
          } finally {
              trafficLock.unlock();
          }
      }
  
      protected void firePacketReceived(final ClientListener client, final Packet packet) {
          if (eventHandler == null) return;
          try {
              packetLock.lock();
              eventHandler.packetReceived(client, packet);
          } finally {
              packetLock.unlock();
          }
      }
  
  }
  </code></pre>
  
  <p>Client:</p>
  
  <pre><code>public class DataClient extends AbstractClient {
  
      private final String host;
      private final int port;
  
      private final RequestPool requestPool = new RequestPool();
      private final PacketManager packetManager = new PacketManager();
      private final ExecutorService thread = Executors.newSingleThreadExecutor();
  
      private ClientFuture future;
      private ServerEventHandler eventHandler;
      private ReconnectionHandler reconnectionHandler;
  
      private String remoteId;
      private long reconnectDelay;
      private boolean reconnectFlag = false;
      private final EventLoopGroup group = new NioEventLoopGroup();
  
      public DataClient(InetAddress host, int port) {
          this(host, port, null);
      }
  
      public DataClient(String host, int port) {
          this(host, port, null);
      }
  
      public DataClient(InetAddress host, int port, ServerEventHandler eventHandler) {
          this(host.getHostAddress(), port, eventHandler);
      }
  
      public DataClient(String host, int port, ServerEventHandler eventHandler) {
          this.host = host;
          this.port = port;
          this.eventHandler = eventHandler;
      }
  
      private void doConnect(final Bootstrap bootstrap) {
          thread.execute(new Runnable() {
              public void run() {
                  try {
                      bootstrap.connect(getByName(host), port).addListener(new ChannelFutureListener() {
                          public void operationComplete(ChannelFuture f) throws Exception {
                              if (f.isSuccess()) {     
                                  channel = f.channel();
                                  send(new HandshakePacket());
                                  } else {
                                  if (future != null) {
                                      future.connectionFailed();
                                  }
                                  attemptReconnect();
                              }
                          }
                      }).sync().channel().closeFuture().sync();
                  } catch (InterruptedException e) {
                      if (future != null) {
                          future.connectionFailed();
                      }
                      attemptReconnect();
                  }
              }
          });
      }
  
      protected Bootstrap configureBootstrap(Bootstrap bootstrap) {
          return bootstrap
          .option(ChannelOption.TCP_NODELAY, true)
          .option(ChannelOption.SO_KEEPALIVE, true)
          .group(group)
          .channel(NioSocketChannel.class)
          .handler(new ChannelInitializer&lt;SocketChannel&gt;() {
              @Override
              public void initChannel(SocketChannel ch) throws Exception {
  //              ch.pipeline().addLast("readTimeoutHandler", new ReadTimeoutHandler(12, TimeUnit.HOURS));
                  ch.pipeline().addLast("frameDecoder", new Varint21FrameDecoder());
                  ch.pipeline().addLast("decoder", new PacketDecoder(packetManager));
                  ch.pipeline().addLast("framePrepender", new Varint21LengthFieldPrepender());
                  ch.pipeline().addLast("encoder", new PacketEncoder(packetManager));
                  ch.pipeline().addLast(new Handler());
              }
          });
      }
  
      public void respond(RequestPacket request, Packet response) {
          send(new ResponsePacket(request.getUniqueId(), response));
      }
  
      public void request(Packet packet, ResponseHandler handler) {
          send(new RequestPacket(requestPool.add(new Request(handler)), packet));
      }
  
      @Override
      public void send(Packet packet) {
          if (!isActive()) {
              throw new DataException("Cannot send packet through closed connection");
          }
          if (!packetManager.isRegistered(packet)) {
              throw new DataException("Cannot send unregistered packet: " + packet.getId());
          }
          channel.writeAndFlush(packet, channel.voidPromise());
      }
  
      @Override
      public String getId() {
          return remoteId;
      }
  
      public void start() {
          start(null);
      }
  
      public void start(ClientFuture future) {
          if (isActive()) {
              throw new DataException("Client already has an active connection");
          }
          this.future = future;
          doConnect(configureBootstrap(new Bootstrap()));
      }
  
      public void stop() {
          if (!isActive()) {
              throw new DataException("Client doesn't have an active connection");
          }
          close();
          group.shutdownGracefully();
          thread.shutdownNow();
          requestPool.clear();
      }
  
      public void setEventHandler(ServerEventHandler eventHandler) {
          this.eventHandler = eventHandler;
      }
  
      public void setReconnectDelay(long delay, TimeUnit unit) {
          reconnectDelay = unit.toMillis(delay);
      }
  
      public void setReconnectHandler(ReconnectionHandler reconnectionHandler) {
          this.reconnectionHandler = reconnectionHandler;
      }
  
      public PacketManager packetManager() {
          return packetManager;
      }
  
      private void attemptReconnect() {
          if (reconnectDelay &gt; 0) {
              group.schedule(new Runnable() {
                  public void run() {
                      reconnectFlag = true;
                      doConnect(configureBootstrap(new Bootstrap()));
                  }
              }, reconnectDelay, TimeUnit.MILLISECONDS);
          }
      }
  
      private final class Handler extends ChannelHandlerAdapter {
  
          @Override
          public void channelInactive(final ChannelHandlerContext ctx) throws Exception { 
              channel = null;
              remoteId = null;
              requestPool.clear();
              if (eventHandler != null) {
                  eventHandler.connectionLost();
              }
              attemptReconnect();
          }
  
          @Override
          public void channelRead(ChannelHandlerContext ctx, Object data) throws Exception {      
              if (data instanceof Packet) {
                  Packet packet = (Packet) data;
                  if (packet instanceof HandshakePacket) {
                      remoteId = ((HandshakePacket) packet).getClientId();
                      if (reconnectFlag &amp;&amp; reconnectionHandler != null) {
                          reconnectionHandler.onReconnect();
                      }
                      if (future != null) {
                          future.onConnect();
                      }
                  } else if (packet instanceof ResponsePacket) {
                      ResponsePacket wp = (ResponsePacket) packet;
                      if (requestPool.hasPending(wp.getUniqueId())) {
                          Request request = requestPool.take(wp.getUniqueId());
                          request.responseHandler().responseReceived(new Response(wp.getPacket(), System.currentTimeMillis() - request.startTime()));
                          } else {
                          if (eventHandler != null) {
                              eventHandler.packetReceived(packet);
                          }
                      }
                  } else {
                      if (eventHandler != null) {
                          eventHandler.packetReceived(packet);
                      }
                  }
              }
          }
  
          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
              e.printStackTrace();
          }
  
      }
  
      private static String getByName(String host) {
          try {
              return InetAddress.getByName(host).getHostAddress();
          } catch (UnknownHostException e) {
              return null;
          }
      }
  
  }
  </code></pre>
  
  <p>Server Handler:</p>
  
  <pre><code>public class ServerHandler extends ChannelHandlerAdapter {
  
      private final DataServer server;
  
      public ServerHandler(DataServer server) {
          this.server = server;
      }
  
      @Override
      public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
          Channel channel = ctx.channel();
          server.channelGroup.add(channel);
      }
  
      @Override
      public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
          Channel channel = ctx.channel();
          server.channelGroup.remove(channel);
          server.fireClientDisconnected(channel);
      }
  
      @Override
      public void channelRead(ChannelHandlerContext ctx, Object data) {
          if (data instanceof Packet) {
              Channel channel = ctx.channel();
              Packet packet = (Packet) data;
              if (packet instanceof HandshakePacket) {
                  server.fireClientConnected(channel);
              } else if (packet instanceof ResponsePacket) {
                  ResponsePacket wp = (ResponsePacket) packet;
                  if (server.requestPool.hasPending(wp.getUniqueId())) {
                      Request request = server.requestPool.take(wp.getUniqueId());
                      request.responseHandler().responseReceived(new Response(wp.getPacket(), System.currentTimeMillis() - request.startTime()));
                      } else {
                      server.firePacketReceived(server.getClient(channel), packet);
                  }
              } else {
                  server.firePacketReceived(server.getClient(channel), packet);
              }
          } else {
              throw new DataException("Received unknown object: " + data.getClass().getSimpleName());
          }
      }
  
      @Override
      public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) {
          if (e instanceof ReadTimeoutException) {
  
          }
          e.printStackTrace();
      }
  
  }
  </code></pre>
  
  <p>Packet Encoder:</p>
  
  <pre><code>public class PacketEncoder extends MessageToByteEncoder&lt;Packet&gt; {
  
      private final PacketManager packetManager;
  
      public PacketEncoder(PacketManager packetManager) {
          this.packetManager = packetManager;
      }
  
      @Override
      protected void encode(ChannelHandlerContext ctx, Packet packet, ByteBuf buf) throws Exception {
          if (packetManager.isRegistered(packet.getId())) {
              Packet.writeVarInt(packet.getId(), buf);
              packet.write(buf);
              } else {
              buf.skipBytes(buf.readableBytes());
              throw new DataException("Cannot send unregistered packet: " + packet.getId());
          }
      }
  
  }
  </code></pre>
  
  <p>Packet Decoder:</p>
  
  <pre><code>public class PacketDecoder extends ByteToMessageDecoder {
  
      private final PacketManager packetManager;
  
      public PacketDecoder(PacketManager packetManager) {
          this.packetManager = packetManager;
      }
  
      @Override
      protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List&lt;Object&gt; out) throws Exception {  
          if (buf.readableBytes() &lt; 4) {
              return;
          }
          int id = Packet.readVarInt(buf);    
          if (packetManager.isRegistered(id)) {
              Packet packet = null;
              if (id == -4) {
                  packet = new ResponsePacket(packetManager);
              } else if (id == -3) {
                  packet = new RequestPacket(packetManager);  
              } else {
                  packet = packetManager.createPacket(id);    
              }
              packet.read(buf);
              if (buf.readableBytes() != 0) {
                  throw new DataException("Did not read all bytes from packet: " + id);
              }
              out.add(packet);
              } else {
              buf.skipBytes(buf.readableBytes());
              throw new DataException("Received unregistered packet: " + id);
          }
      }
  
  }
  </code></pre>
  
  <p>Frame Decoder:</p>
  
  <pre><code>    public class Varint21FrameDecoder extends ByteToMessageDecoder {
  
          @Override
          protected void decode(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out) throws Exception {
              in.markReaderIndex();
              byte[] buf = new byte[8];
              for (int i = 0; i &lt; buf.length; i++) {
                  if (!in.isReadable()) {
                      in.resetReaderIndex();
                      return;
                  }
                  buf[i] = in.readByte();
                  if (buf[i] &gt;= 0) {
                      int len = Packet.readVarInt(Unpooled.wrappedBuffer(buf));
                      if (in.readableBytes() &lt; len) {
                          in.resetReaderIndex();
                          return;
                          } else {
                          out.add(in.readBytes(len));
                          return;
                      }
                  }
              }
              throw new CorruptedFrameException("Length wider than 21-bit");
          }
  
      }
  </code></pre>
  
  <p>Frame Prepender:</p>
  
  <pre><code>public class Varint21LengthFieldPrepender extends MessageToByteEncoder&lt;ByteBuf&gt; {
  
      @Override
      protected void encode(ChannelHandlerContext ctx, ByteBuf buf, ByteBuf out) throws Exception {
          int bodyLen = buf.readableBytes();
          int headerLen = varIntSize(bodyLen);
          out.ensureWritable(headerLen + bodyLen);
          Packet.writeVarInt(bodyLen, out);
          out.writeBytes(buf);
      }
  
      private int varIntSize(int i) {
          if ((i &amp; 0xFFFFFF80) == 0) {
              return 1;
          } else if ((i &amp; 0xFFFFC000) == 0) {
              return 2;
          } else if ((i &amp; 0xFFE00000) == 0) {
              return 3;
          } else if ((i &amp; 0xF0000000) == 0) {
              return 4;
          }
          return 5;
      }
  
  }
  </code></pre>
  
  <p>An error that occurred as it sent a packet after being inactive for a bit:</p>
  
  <pre><code>   May 10, 2014 1:48:37 AM io.netty.handler.logging.LoggingHandler flush
      INFO: [id: 0x83a23b65, /192.168.1.130:60375 =&gt; /173.10.74.78:12001] FLUSH
      May 10, 2014 1:48:46 AM io.netty.handler.logging.LoggingHandler exceptionCaught
      INFO: [id: 0x83a23b65, /192.168.1.130:60375 =&gt; /173.10.74.78:12001] EXCEPTION: java.io.IOException: Operation timed out
      java.io.IOException: Operation timed out
          at sun.nio.ch.FileDispatcher.read0(Native Method)
          at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:21)
          at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:198)
          at sun.nio.ch.IOUtil.read(IOUtil.java:166)
          at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:245)
          at io.netty.buffer.UnpooledUnsafeDirectByteBuf.setBytes(UnpooledUnsafeDirectByteBuf.java:446)
          at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:871)
          at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)
          at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:119)
          at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
          at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
          at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
          at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
          at java.lang.Thread.run(Thread.java:695)
      java.io.IOException: Operation timed out
          at sun.nio.ch.FileDispatcher.read0(Native Method)
          at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:21)
          at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:198)
          at sun.nio.ch.IOUtil.read(IOUtil.java:166)
          at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:245)
          at io.netty.buffer.UnpooledUnsafeDirectByteBuf.setBytes(UnpooledUnsafeDirectByteBuf.java:446)
          at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:871)
          at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)
          at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:119)
          at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
          at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
          at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
          at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
          at java.lang.Thread.run(Thread.java:695)
      May 10, 2014 1:48:46 AM io.netty.handler.logging.LoggingHandler channelInactive
      INFO: [id: 0x83a23b65, /192.168.1.130:60375 :&gt; /173.10.74.78:12001] INACTIVE
  </code></pre>