在短暂的客户端不活动时间后,Netty服务器停止接收数据包

时间:2014-05-10 06:19:40

标签: java tcp io netty

我正在制作一个TCP客户端 - 服务器项目,我遇到了一些问题。当我将客户端连接到服务器时,它在客户端处于活动状态并发送一些数据包(从swing触发事件发送的数据包)时完美运行。如果我让我的应用程序停留大约10-15分钟,然后尝试再次使用它,客户端说它发送了数据包,但服务器没有收到它们。我甚至完全关闭了客户端,服务器仍然说它已连接。我真的需要让客户端能够在用户处于非活动状态时保持闲置状态,并且仍能够在将来工作。任何一方都没有错误,我已经尝试了所有可以找到的东西,SO_KEEPALIVE,IdleStateHandler,ReadTimeoutHandler等。服务器在一些不活动后仍然停止接收数据包。

这是客户端/服务器的主要代码。

由于

服务器:

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<String, ClientListener> clients = new ConcurrentHashMap<String, ClientListener>();

    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<SocketChannel>() {
                @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<ClientListener> activeClients() {
        return new HashSet<ClientListener>(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();
        }
    }

}

客户端:

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

}

服务器处理程序:

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

}

数据包编码器:

public class PacketEncoder extends MessageToByteEncoder<Packet> {

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

}

数据包解码器:

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<Object> out) throws Exception {  
        if (buf.readableBytes() < 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);
        }
    }

}

帧解码器:

    public class Varint21FrameDecoder extends ByteToMessageDecoder {

        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            in.markReaderIndex();
            byte[] buf = new byte[8];
            for (int i = 0; i < buf.length; i++) {
                if (!in.isReadable()) {
                    in.resetReaderIndex();
                    return;
                }
                buf[i] = in.readByte();
                if (buf[i] >= 0) {
                    int len = Packet.readVarInt(Unpooled.wrappedBuffer(buf));
                    if (in.readableBytes() < len) {
                        in.resetReaderIndex();
                        return;
                        } else {
                        out.add(in.readBytes(len));
                        return;
                    }
                }
            }
            throw new CorruptedFrameException("Length wider than 21-bit");
        }

    }

Frame Prepender:

public class Varint21LengthFieldPrepender extends MessageToByteEncoder<ByteBuf> {

    @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 & 0xFFFFFF80) == 0) {
            return 1;
        } else if ((i & 0xFFFFC000) == 0) {
            return 2;
        } else if ((i & 0xFFE00000) == 0) {
            return 3;
        } else if ((i & 0xF0000000) == 0) {
            return 4;
        }
        return 5;
    }

}

在一个位处于非活动状态后发送数据包时发生的错误:

   May 10, 2014 1:48:37 AM io.netty.handler.logging.LoggingHandler flush
    INFO: [id: 0x83a23b65, /192.168.1.130:60375 => /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 => /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 :> /173.10.74.78:12001] INACTIVE

0 个答案:

没有答案