2017-10-06 14 views
0

J'utilise Netty.io (4.0.4) dans une application Java pour implémenter un client TCP pour communiquer avec un pilote matériel externe. L'une des exigences de ce matériel est que le client envoie un message KEEP_ALIVE (heart-beat) toutes les 30 secondes, le matériel ne répond cependant pas à ce battement de chaleur. Mon problème est que lorsque la connexion est brusquement interrompue (par exemple: câble réseau débranché), le client ne le sait pas et continue à envoyer le message KEEP_ALIVE pendant plus longtemps (environ 5-10 minutes) avant d'obtenir une exception de délai d'attente. En d'autres termes, du côté du client, il n'y a aucun moyen de dire si c'est encore connecté.Netty client prend très longtemps avant que le réseau brisé est détecté

est Ci-dessous un extrait de ma configuration bootstrap si elle aide

// bootstrap setup 
bootstrap = new Bootstrap().group(group) 
      .channel(NioSocketChannel.class) 
      .option(ChannelOption.SO_KEEPALIVE, true) 
      .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 3000) 
      .remoteAddress(ip, port) 
      .handler(tcpChannelInitializer); 


// part of the pipeline responsible for keep alive messages 
    pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS)); 
    pipeline.addLast("keepAliveHandler", keepAliveMessageHandler); 

Je me attends depuis le client envoie conserver les messages en vie, et ces messages ne sont pas reçus à l'autre bout, un accusé de réception manquant doit indiquer un problème dans la connexion beaucoup plus tôt?

EDIT

code de la KeepAliveMessageHandler

public class KeepAliveMessageHandler extends ChannelDuplexHandler 
{ 

    private static final Logger LOGGER = getLogger(KeepAliveMessageHandler.class); 

    private static final String KEEP_ALIVE_MESSAGE = ""; 


    @Override 
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception 
    { 
     if (!(evt instanceof IdleStateEvent)) { 
      return; 
     } 

     IdleStateEvent e = (IdleStateEvent) evt; 
     Channel channel = ctx.channel(); 

     if (e.state() == IdleState.ALL_IDLE) { 
      LOGGER.info("Sending KEEP_ALIVE_MESSAGE"); 
      channel.writeAndFlush(KEEP_ALIVE_MESSAGE); 
     } 
    } 
} 

EDIT 2

Je fatigué pour s'assurer explicitement le garder un message vivant livré en utilisant le code ci-dessous

@Override 
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception 
    { 
     if (!(evt instanceof IdleStateEvent)) { 
      return; 
     } 

     IdleStateEvent e = (IdleStateEvent) evt; 
     Channel channel = ctx.channel(); 

     if (e.state() == IdleState.ALL_IDLE) { 
      LOGGER.info("Sending KEEP_ALIVE_MESSAGE"); 
      channel.writeAndFlush(KEEP_ALIVE_MESSAGE).addListener(future -> { 

       if (!future.isSuccess()) { 
        LOGGER.error("KEEP_ALIVE message write error"); 
        channel.close(); 
       } 
      }); 
     } 
    } 

Cela ne fonctionne pas non plus. :(selon this answer ce comportement a du sens, mais j'espère toujours qu'il y a un moyen de comprendre si l'écriture était un "vrai" succès (ayant l'ack matériel le hear-beat n'est pas possible)

+1

peut-être prendre un coup d'oeil à la réponse ici? https://stackoverflow.com/questions/21358800/tcp-keep-alive-to-determine-if-client-disconnected-in-netty –

+0

merci pour ce lien, j'ai regardé avant que je pose la question, les questions que je avoir avec cette solution sont: a. puisque le câble réseau est débranché, aucune fermeture normale de canal n'est possible b. implémentant le ReadTimeoutHandler ne fonctionnera pas, parce que le matériel ne dit pas grand chose, donc cela serait déclenché trop souvent:/(l'ack dont je parle dans la question est TCP couche ack pas au niveau de l'application). Avoir du sens? Peut-être que ce que je veux n'est même pas possible par TCP, et c'est une partie de la question. – codeCruncher

+0

Je m'attendrais à ce que vous obteniez une «réinitialisation de connexion» ou une «interruption de connexion causée par logiciel» après quelques minutes. Êtes-vous sûr de détecter correctement les erreurs d'envoi lorsque vous envoyez les audioconférences? – EJP

Répondre

0

vous avez activé le protocole TCP keepalive

.option(ChannelOption.SO_KEEPALIVE, true) 

Mais dans votre code, je ne vois aucune pièce qui assure keepalive à envoyer à une vitesse de 30 secondes.

Si une connexion a été interrompue en raison d'un TCP Keepalive time-out et l'autre hôte envoie finalement un paquet pour l'ancienne connexion, L'hôte qui a mis fin à la connexion enverra un paquet avec l'indicateur RST défini pour signaler à l'autre hôte que l'ancienne connexion n'est plus active. Cela forcera l'autre hôte à terminer sa fin de la connexion afin qu'une nouvelle connexion puisse être établie.

Généralement, les valeurs Keepalives TCP sont envoyées toutes les 45 ou 60 secondes sur une connexion TCP inactive et la connexion est interrompue après l'échec de 3 ACK séquentiels. Cela varie en fonction de l'hôte, par ex. Par défaut, les PC Windows envoient le premier paquet TCP Keepalive après 7200000ms (2 heures), puis envoient 5 Keepalives à intervalles de 1000ms, abandonnant la connexion s'il n'y a pas de réponse à l'un des paquets Keepalive.

(forme prise http://ltxfaq.custhelp.com/app/answers/detail/a_id/1512/~/tcp-keepalives-explained_

Je ne comprends maintenant que

pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 30, TimeUnit.SECONDS)); 
pipeline.addLast("keepAliveHandler", keepAliveMessageHandler); 

déclenche un événement de repos toutes les 30 secondes sur l'inactivité mutuelle et keepAliveMessageHandler sera envoyé un paquet à retirer côté dans ce cas.

Malheureusement

ChannelFuture future = channel.writeAndFlush(KEEP_ALIVE_MESSAGE); 

est considéré comme un succès lorsqu'il est écrit dans des tampons OS.

Il semble que dans vos conditions vous avez seulement 2 OptioS:

  1. Envoi d'une commande qui aura une réponse de dispositif externe (ce qui ne causera pas distruption)
    Mais je suppose que C'est impossible dans votre cas. Modification des paramètres de pilote TCP sous-jacents
    Les paramètres de système d'exploitation par défaut pour TCP keepalive concernent davantage la conservation des ressources système pour prendre en charge une grande quantité d'applications et de connexions. Pourvu que vous ayez un système dédié, vous pouvez configurer des vérifications TCP plus agressives. Voici le lien sur la façon de faire des ajustements au noyau Linux: http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/usingkeepalive.html
    La solution devrait fonctionner comme sur les installations simples aussi bien dans les VM que dans les conteneurs Docker.

Informations générales sur le sujet: https://blog.stephencleary.com/2009/05/detection-of-half-open-dropped.html

+0

J'ai jeté un oeil à la publication SO, et comme je l'ai dit dans le commentaire suivant, la solution ne fonctionnera pas (je l'ai essayée, elle jette une exception s'il n'y a pas de lecture, cela ne signifie pas nécessairement la connexion est mort, ce qui n'est pas ce que je veux). J'ai également ajouté le code pour mon KeepAliveHandler à la question originale. J'apprécie votre effort pour aider – codeCruncher

+0

Maintenant c'est une histoire différente. J'ai une mise à jour pour vous.Si ce n'est pas utile, ajoutez des informations sur votre délai d'envoi, le nombre de tentatives et ce que votre KEEP_ALIVE_MESSAGE est exactement. –

+0

J'ai essayé à la manipulation du ChannelFuture qui est retourné après l'appel, comme writeAndFlush() ce: channel.writeAndFlush (KEEP_ALIVE_MESSAGE) .addListener (futur -> { if (future.isSuccess()) { ENREGISTREUR .error ("erreur d'écriture de message KEEP_ALIVE"); channel.close(); } }); mais cela ne fonctionne pas, le bloc if n'est pas exécuté! J'ai lu que netty dit le succès lorsque les données ont été écrites dans le tampon d'E/S, pas quand elles sont reçues à l'autre extrémité. – codeCruncher