Java NIO: Comment savoir quand SocketChannel read() est complète avec des non-blocage I/O
Je suis actuellement en utilisant un non-bloquant SocketChannel (Java 1.6) pour agir comme un client à un serveur Redis. Redis accepte plaine-des commandes de texte directement sur une prise, terminées par un CRLF et réagit comme un exemple rapide:
ENVOYER: "PING\r\n'
RECV: '+PONG\r\n'
Redis pouvez également retourner énorme réponses (en fonction de ce que vous demandez) avec de nombreuses sections de \r\n-résiliation de données qui font tous partie d'une seule et même réponse.
Je suis en utilisant une norme while(socket.read() > 0) {//ajout d'octets} boucle pour lire des octets à partir de la douille et de ré-assembler côté client dans une réponse.
NOTE: je ne suis pas à l'aide d'un Sélecteur, juste plusieurs, côté client SocketChannels connecté au serveur, en attente de service envoyer/recevoir des commandes.
Ce que je suis confus au sujet de qui est le contrat de la SocketChannel.read() méthode en mode sans blocage, plus précisément, comment savoir quand le serveur d'envoi et j'ai le message en entier.
J'ai quelques méthodes pour protéger contre le retour des trop vite et donner une chance de répondre, mais la seule chose que je suis bloqué sur est:
- Est-il jamais possible pour read() de retour octets, puis ultérieurement sur un retour d'appel de n octets, mais sur un autre appel ultérieur à nouveau de retour de quelques octets?
En gros, puis-je faire confiance que le serveur est en fait de répondre à moi si j'ai reçu au moins 1 octet, et finalement read() retourne 0 alors je sais que je suis fait, ou est-il possible que le serveur était occupé et peut-chute en arrière de quelques octets si je attendre et de continuer à essayer?
Si il peut continuer à envoyer des octets, même après une lecture() a renvoyé 0 octets (après précédentes lectures réussies), alors je n'ai aucune idée de comment savoir si le serveur est fait de me parler et de me confondre java.io.* le style de communication ne sais même lorsque le serveur est "fait", soit.
Que vous les gars savent lire ne retourne -1, sauf si la connexion est mort et ce sont standard à long terme DB connections, donc je ne serai pas de fermeture et d'ouverture sur chaque demande.
Je sais une réponse populaire (au moins pour ces NIO questions) ont été à regarder Grizzly, MINA ou Netty -- si possible, j'aimerais vraiment savoir comment tout cela fonctionne dans son état brut, avant l'adoption de certaines 3ème partie des dépendances.
Merci.
Question Bonus:
J'ai pensé à l'origine d'un blocage SocketChannel serait le chemin pour aller avec ce que je ne veux pas vraiment à l'appelant de faire quelque chose jusqu'à ce que je leur commande et de leur donner une réponse, de toute façon.
Si cela finit par être une meilleure façon d'y aller, j'ai été un peu confus en voyant que SocketChannel.lire() bloque aussi longtemps qu'il n'y a pas d'octets suffisante pour remplir le tampon... bref de la lecture de tout, octet par octet, je ne peux pas comprendre comment ce comportement par défaut est en fait destiné à être utilisé... je ne sais jamais le exacte la taille de la réponse est de retour à partir du serveur, donc mes appels à SocketChannel.read() toujours bloquer jusqu'à ce qu'un temps (à quel point je vois enfin que le contenu était assis dans la mémoire tampon).
Je ne suis pas très clair sur la bonne façon d'utiliser le blocage de la méthode, car il bloque toujours sur une lecture.
- Ce n'est pas lié à NIO mais un protocole doit soit utiliser fixe la longueur des messages, envoyer la longueur du message de la première ou à la fin de chaque message unique avec un délimiteur.
- Donc, en général, vous dites, à moins que j'ai des séparateurs ou des longueurs de travailler avec, je suis SOL? Le Redis protocole ne fait préciser ces choses, mais je dois convertir des octets pour les caractères, pièce repas, à la volée pour vérifier ces valeurs que j'ai lu, faisant de mon code réseau un enfer de beaucoup plus compliqué. J'espérais que je pourrais l'éviter.
- J'ai peur que c'est le seul moyen sûr mais ce n'est pas si mauvais que ça. Aussi loin que je peux rassembler, le Redis protocole à la fin de chaque commande/répondre élément avec un CR/LF, de sorte que vous pouvez lire et d'accumuler des octets lors de la vérification pour CR/LF et de convertir votre tampon de caractères à la fin de chaque ligne.
- biziclop - très bonne suggestion, je n'avais pas réellement pensé pour groupe sur \r\n limites, mais qui permettrait d'éviter que l'un octet-char de conversion les cas, je étais inquiet au sujet de (tentative de conversion unicode char avant de tous les octets sont disponibles). Merci!!!!
Vous devez vous connecter pour publier un commentaire.
De lire et de suivre le protocole:
http://redis.io/topics/protocol
La spécification décrit les types de réponses et comment les reconnaître. Certains sont en ligne résiliée, tout en multi-ligne réponses inclure un préfixe comte.
Oui, c'est possible. Pas seulement dû au serveur d'être occupé, mais la congestion du réseau et arraché des routes, les données risquent d'en "pause". Les données est un flux qui peuvent "mettre en pause" n'importe où dans le flux sans relation avec l'application du protocole.
Continuez à lire le flux dans une mémoire tampon. Coup d'oeil sur le premier caractère de déterminer quel type de réponse à attendre. Examiner la mémoire tampon après chaque succès de la lecture jusqu'à ce que le tampon contient la totalité du message en fonction des spécifications.
Je pense que vous avez raison. Basé sur mon rapide regarder les spec, le blocage de lit ne serait pas travailler pour ce protocole. Car il est basé sur la ligne, BufferedReader peut aider, mais vous avez encore besoin de savoir comment reconnaître si la réponse est complète.
Look à votre Redis spécifications pour répondre à cette question.
Il n'est pas contre les règles pour un appel à
.read()
pour retourner 0 octets sur un simple appel, et 1 ou plusieurs octets sur un appel ultérieur. C'est parfaitement légal. Si quelque chose avait été à l'origine d'un retard dans la livraison, soit en raison de la latence réseau ou de lenteur dans le Redis serveur, cela pourrait se produire.La réponse que vous cherchez est la même réponse à la question: "Si je suis connecté manuellement pour le Redis serveur et envoyé une commande, comment pourrais-je savoir quand il a été fait de l'envoi de la réponse pour moi afin que je puisse envoyer une autre commande?"
La réponse doit être trouvée dans le Redis spécification. Si il n'y a pas un jeton que le serveur envoie quand c'est fait de l'exécution de votre commande, alors ce peut être mis en œuvre sur une commande par commande de base. Si le Redis spécifications ne le permettent pas, alors c'est un défaut dans le Redis cahier des charges. Ils doivent vous dire comment le dire quand ils ont envoyé tous leurs données. C'est pourquoi les coquilles ont l'invite de commande. Redis doit avoir un équivalent.
Dans le cas où le Redis n'ont pas dans leurs spécifications, alors je suggère de mettre dans une sorte de timers. Code votre de la manipulation du fil le support pour signaler que la commande est terminée après aucune donnée n'a été reçue pour une période de temps, comme les cinq secondes. Choisissez une période de temps qui est nettement plus longue que la plus longue de commande est à exécuter sur le serveur.
Qui n'est pas une technique classique de NIO. Vous doit stocker le résultat de la lecture dans une variable, et de le tester pour:
Il a été un long moment, mais . . .
Juste pour être clair, SocketChannels sont de blocage par défaut; pour les rendre non-blocage, il faut appeler explicitement
SocketChannel#configureBlocking(false)
Je vais supposer que vous n'avez
Whoa; c'est ça le problème; si vous allez à l'utilisation non-blocage des Canaux, alors vous devriez toujours utiliser un Sélecteur (au moins pour les lectures); sinon, vous courez dans la confusion que vous avez décrit, viz.
read(ByteBuffer) == 0
ne veut rien dire (eh bien, cela signifie qu'il n'y a pas d'octets dans le tampon de réception tcp à ce moment).C'est analogue à la vérification de votre boîte aux lettres et c'est vide, ça veut dire que la lettre n'arrivera jamais? n'a jamais été envoyée?
Il y a un contrat -> si un Sélecteur a sélectionné un Canal pour une opération de lecture, alors la prochaine invocation de
SocketChannel#read(ByteBuffer)
est garanti pour revenir > 0 (en supposant qu'il de la place dans la ByteBuffer arg)Qui est pourquoi vous utilisez un Sélecteur, et parce qu'il peut dans un appel à select ", sélectionnez" 1Ks de SocketChannels qui ont octets prêt à lire
Maintenant il n'y a rien de mal à utiliser le SocketChannels dans leur blocage par défaut mode; et compte tenu de votre description (un client ou deux), il n'y a probablement pas de raison pour que sa plus simple; mais si vous voulez utiliser le non-blocage des Canaux, utilisez un Sélecteur