简单方案:
顶级B类,系统的其他部分可以使用它来发送和接收消息(可以同时模拟同步和异步)。此类创建ClientBootstrap,设置管道工厂,调用bootstrap.connect()并最终获得A类的句柄/引用,用于发送和接收消息。类似的东西:
ChannelFuture future = bootstrap.connect();
Channel channel = future.awaitUninterruptibly().getChannel();
处理程序= channel.getPipeline()。get(A.class);
我知道在A班,我可以覆盖 public void channelClosed(ChannelHandlerContext ctx,ChannelStateEvent e); 这样当远程服务器关闭时,我会收到通知。
由于在关闭通道之后,B类中的原始类A引用(上面的处理程序)不再有效,因此我需要用新引用替换它。
理想情况下,我希望类A具有在上面覆盖的channelClosed方法中通知B类的机制,以便可以在B类中再次调用bootstrap.connect。一种方法是在A类中引用一个为此,我需要将B类引用传递给PipelineFactory,然后让PipelineFactory将B的引用传递给A。
任何其他更简单的方法来实现同样的目标?
感谢,
答案 0 :(得分:15)
Channel.closeFuture()
会返回ChannelFuture
,会在频道关闭时通知您。您可以在B中为将来添加ChannelFutureListener
,以便您可以在那里进行另一次连接尝试。
您可能希望重复此操作,直到最后连接尝试成功:
private void doConnect() {
Bootstrap b = ...;
b.connect().addListener((ChannelFuture f) -> {
if (!f.isSuccess()) {
long nextRetryDelay = nextRetryDelay(...);
f.channel().eventLoop().schedule(nextRetryDelay, ..., () -> {
doConnect();
}); // or you can give up at some point by just doing nothing.
}
});
}
答案 1 :(得分:0)
我经过长时间尝试使其手动执行。线程是另一种野兽。无论如何,这就是我如何应对这一挑战。
这是客户:
import java.util.logging.Logger;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class NettyClient implements Runnable {
Logger logger = Logger.getLogger(NettyClient.class.getName());
boolean done = false;
public void run() {
String host = "10.99.1.249";
int port = 7973;
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder(), new StringDecoder(),
new DelimiterBasedFrameDecoder(8192, false, Unpooled.wrappedBuffer("</tnt>".getBytes())),
new ClientHandler(getClient()));
}
});
ChannelFuture f = b.connect(host, port).sync();
Channel ch = f.channel();
ChannelFuture f5 = b.bind(ch.localAddress());
while(!done) {
Thread.sleep(5000);
}
System.out.println("Done.");
}
catch (InterruptedException e) {
logger.info(e.getMessage());;
}
finally {
workerGroup.shutdownGracefully();
}
}
public boolean isDone() {
return done;
}
public void setDone(boolean done) {
this.done = done;
}
private NettyClient getClient() {
return this;
}
}
这是对应的ClientHandler,它完成重新连接的实际工作:
import java.net.SocketAddress;
import java.util.logging.Logger;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class ClientHandler extends ChannelInboundHandlerAdapter {
Logger logger = Logger.getLogger(ClientHandler.class.getName());
private NettyClient client;
public ClientHandler(NettyClient client) {
super();
this.client = client;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel ch = ctx.channel();
ChannelFuture f2 = ch.writeAndFlush("</tnt>");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// invoked when there is a problem connecting to remote server; needs to be enhanced to properly handle thread shutdown
client.setDone(true);
SocketAddress sa = ctx.channel().remoteAddress();
while(true) {
try {
NettyClient nc = new NettyClient();
nc.run();
break;
} catch(Exception ex) {
logger.info("Reconnecting...");
}
Thread.sleep(5000);
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// incoming notifications are handled here
System.out.println((String)msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
实际的重新连接由channelInactive()
方法处理。
答案 2 :(得分:0)
这是将重新连接行为封装在一个小的帮助程序类中的另一个版本
Bootstrap clientBootstrap...
EventLoopGroup group = new NioEventLoopGroup();
Session session = new Session(clientBootstrap,group);
Disposable shutdownHook = session.start();
interface Disposable {
void dispose();
}
class Session implements Disposable{
private final EventLoopGroup scheduler;
private final Bootstrap clientBootstrap;
private int reconnectDelayMs;
private Channel activeChannel;
private AtomicBoolean shouldReconnect;
private Session(Bootstrap clientBootstrap, EventLoopGroup scheduler) {
this.scheduler = scheduler;
this.clientBootstrap = clientBootstrap;
this.reconnectDelayMs = 1;
this.shouldReconnect = new AtomicBoolean(true);
}
public Disposable start(){
//Create a new connectFuture
ChannelFuture connectFuture = clientBootstrap.connect();
connectFuture.addListeners( (ChannelFuture cf)->{
if(cf.isSuccess()){
L.info("Connection established");
reconnectDelayMs =1;
activeChannel = cf.channel();
//Listen to the channel closing
var closeFuture =activeChannel.closeFuture();
closeFuture.addListeners( (ChannelFuture closeFut)->{
if(shouldReconnect.get()) {
activeChannel.eventLoop().schedule(this::start, nextReconnectDelay(), TimeUnit.MILLISECONDS);
}
else{
L.info("Session has been disposed won't reconnect");
}
});
}
else{
int delay =nextReconnectDelay();
L.info("Connection failed will re-attempt in {} ms",delay);
cf.channel().eventLoop().schedule(this::start,delay , TimeUnit.MILLISECONDS);
}
});
return this;
}
/**
* Call this to end the session
*/
@Override
public void dispose() {
try {
shouldReconnect.set(false);
scheduler.shutdownGracefully().sync();
if(activeChannel !=null) {
activeChannel.closeFuture().sync();
}
}catch(InterruptedException e){
L.warn("Interrupted while shutting down TcpClient");
}
}
private int nextReconnectDelay(){
this.reconnectDelayMs = this.reconnectDelayMs*2;
return Math.min(this.reconnectDelayMs, 5000);
}
}
答案 3 :(得分:0)
我不知道这是否是正确的解决方案,但为了修复 trustin 解决方案的线程泄漏,我发现我可以在调度程序触发后关闭事件循环:
final EventLoop eventloop = f.channel().eventLoop();
b.connect().addListener((ChannelFuture f) -> {
if (!f.isSuccess()) {
long nextRetryDelay = nextRetryDelay(...);
eventloop.schedule(() -> {
doConnect();
eventloop.shutdownGracefully();
}, nextRetryDelay, ...);
}
});