One of the first requirement of Netty ISO8588 client connector is the support for automatic reconnect.
One of the first receipts I came across was Thomas Termin’s one. He suggests adding a ChannelHandler which will schedule the calling of client’s connect() method once a Channel becomes inactive. Plus adding ChannelFutureListener which will re-create a bootstrap and re-connect if initial connection was failed.
Although this is a working solution, I had a feeling that something is not optimal. Namely, the new Bootstrap is being created on every connection attempt.
So, I created a FutureListener which should be registered once a Channel is closed.
Here is the ReconnectOnCloseListener code:
1 public class ReconnectOnCloseListener implements ChannelFutureListener {
2
3 private final Logger logger = getLogger(ReconnectOnCloseListener.class);
4
5 private final Iso8583Client client;
6 private final int reconnectInterval;
7 private final AtomicBoolean disconnectRequested = new AtomicBoolean(false);
8 private final ScheduledExecutorService executorService;
9
10 public ReconnectOnCloseListener(Iso8583Client client, int reconnectInterval, ScheduledExecutorService executorService) {
11 this.client = client;
12 this.reconnectInterval = reconnectInterval;
13 this.executorService = executorService;
14 }
15
16 public void requestReconnect() {
17 disconnectRequested.set(false);
18 }
19
20 public void requestDisconnect() {
21 disconnectRequested.set(true);
22 }
23
24 @Override
25 public void operationComplete(ChannelFuture future) throws Exception {
26 final Channel channel = future.channel();
27 logger.debug("Client connection was closed to {}", channel.remoteAddress());
28 channel.disconnect();
29 scheduleReconnect();
30 }
31
32 public void scheduleReconnect() {
33 if (!disconnectRequested.get()) {
34 logger.trace("Failed to connect. Will try again in {} millis", reconnectInterval);
35 executorService.schedule(
36 client::connectAsync,
37 reconnectInterval, TimeUnit.MILLISECONDS);
38 }
39 }
40 }To establish the connection I use the following code:
1 reconnectOnCloseListener.requestReconnect();
2 final ChannelFuture connectFuture = bootstrap.connect();
3 connectFuture.addListener(connFuture -> {
4 if (!connectFuture.isSuccess()) {
5 reconnectOnCloseListener.scheduleReconnect();
6 return;
7 }
8 Channel channel = connectFuture.channel();
9 logger.info("Client is connected to {}", channel.remoteAddress());
10 setChannel(channel);
11 channel.closeFuture().addListener(reconnectOnCloseListener);
12 });
13 connectFuture.sync();// if you need to connect synchronouslyWhen you want to disconnect, you’ll need to disable automatic reconnection first:
1 reconnectOnCloseListener.requestDisconnect();
2 channel.close();The solution works fine so far (integration test).
Another option is to add a ChannelOutboundHandler which will handle disconnects.
Links
- Sources: ReconnectListener, Client
- StackOverflow: answer one, answer two