我正在制作一个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