Peaufiner les performances de Java sockets
J'ai créé un contrôle de bureau à distance de l'application. Évidemment, il se compose de client et de serveur de pièces:
Serveur:
- De la réception de la souris/du clavier à partir d'un client;
- L'envoi de capture d'écran de desktop client.
Client:
- Réception capture d'écran du serveur;
- L'envoi de la souris/du clavier;
Envisager d'envoyer la capture d'écran. Lorsque j'utilise mon PC à la maison en tant que serveur, je me retrouve avec une capture d'écran de 1920x1080 dimension. En utilisant JAI Image I/O Outils et l'encodage au format PNG, j'ai pu atteindre les stats suivantes pour une telle image:
- Écrire le temps de ~0,2 s; (pas dans la douille, mais sur un "régulier" flux de sortie, je. e. moment de l'encodage)
- Temps de lecture ~0,05 s; (pas de prise, mais d'un "régulier" flux d'entrée, je. e. le temps de décodage)
- Taille ~250 KO;
- Qualité parfaite.
Comme un résultat, selon #1 - l'idéal possible FPS doit être d'environ 5.
Malheureusement, je ne suis pas en mesure d'atteindre même ceux ~5 FPS, même pas 2 FPS. J'ai cherché le goulot d'étranglement et a trouvé que l'écriture/la lecture/de socket flux e/S prend jusqu'à ~2 s (Voir annexe 1 et 2 pour des précisions). Sûrement, c'est inacceptable.
J'ai étudié le sujet un peu et ajouté de mise en mémoire tampon de la socket d'e/S de flux (avec BufferedInputStream
et BufferedOutputStream
) sur les deux côtés. J'ai commencé avec 64 KO de tailles. En effet, l'amélioration de la performance. Mais ne peut toujours pas avoir au moins 2 FPS! En outre, j'ai expérimenté avec Socket#setReceiveBufferSize
et Socket#setSendBufferSize
et il y avait quelques changements dans la vitesse, mais je ne sais pas exactement comment ils se comportent, donc je ne sais pas les valeurs à utiliser.
Regarder le code d'initialisation:
Serveur:
ServerSocket serverSocket = new ServerSocket();
serverSocket.setReceiveBufferSize( ? ); //#1
serverSocket.bind(new InetSocketAddress(...));
Socket clientSocket = serverSocket.accept();
clientSocket.setSendBufferSize( ? ); //#2
clientSocket.setReceiveBufferSize( ? ); //#3
OutputStream outputStream = new BufferedOutputStream(
clientSocket.getOutputStream(), ? ); //#4
InputStream inputStream = new BufferedInputStream(
clientSocket.getInputStream(), ? ); //#5
Client:
Socket socket = new Socket(...);
socket.setSendBufferSize( ? ); //#6
socket.setReceiveBufferSize( ? ); //#7
OutputStream outputStream = new BufferedOutputStream(
socket.getOutputStream(), ? ); //#8
InputStream inputStream = new BufferedInputStream(
socket.getInputStream(), ? ); //#9
Questions:
- Quelles valeurs voulez-vous recommander (pour améliorer les performances) pour tous les
ces cas et pourquoi? - Préciser
Socket#setReceiveBufferSize
et
Socket#setSendBufferSize
comportement. - Ce que d'autres méthodes ou techniques peuvent vous conseiller pour améliorer les performances
d'une telle application? - Skype offre une bonne qualité de bureau en temps réel de transmission - comment font-ils?
Annexe 1: Ajoutant le déroulé de la pseudo-code du client, de la prise de la lecture (@mcfinnigan):
while(true) {
//objectInputStream is wrapping socket's buffered input stream.
Object object = objectInputStream.readObject(); //<--- Bottleneck (without setting proper buffer sizes is ~2 s)
if(object == null)
continue;
if(object.getClass() == ImageCapsule.class) {
ImageCapsule imageCapsule = (ImageCapsule)object;
screen = imageCapsule.read(); //<--- Decode PNG (~0.05 s)
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
repaint();
}
});
}
}
Annexe 2: Ajoutant le déroulé de la pseudo-code de la socket du serveur d'écriture (@EJP):
while(true) {
//objectOutputStream is wrapping socket's buffered output stream.
BufferedImage screen = ... //obtaining screenshot
ImageCapsule imageCapsule = new ImageCapsule();
imageCapsule.write(screen, formatName()); //<--- Encode PNG (~0.2 s)
try {
objectOutputStream.writeObject(imageCapsule); //<--- Bottleneck (without setting proper buffer sizes is ~2 s)
}
finally {
objectOutputStream.flush();
objectOutputStream.reset(); //Reset to free written objects.
}
}
Conclusion:
Merci pour vos réponses, spécialement EJP - il a fait les choses un peu plus clair pour moi. Si vous êtes comme moi, en quête de réponses sur la manière d'ajuster les sockets de la performance, vous devriez certainement vérifier TCP/IP Sockets en Java, Deuxième Édition: Guide Pratique pour les Programmeurs, en particulier le chapitre 6 "Sous le Capot", qui décrit ce qui se passe derrière les coulisses de *Socket
les classes, comment envoyer et recevoir des tampons sont gérées et utilisées (qui sont les principales clés de la performance).
- Êtes-vous sûr que la prise paramètres sont vos facteurs limitants? Quel type de connexion avez-vous entre votre client et votre serveur? Quelle est la vitesse de votre serveur?
- Pouvez-vous poster le code où vous lisez à partir de la sockets sur le côté client?
- en.wikipedia.org/wiki/Delta_encoding
- Lindsjö Que pensez-vous que vous entendez par "type de connexion"? Comment puis-je mesurer la vitesse de mon serveur?
- Pourquoi êtes-vous en testant la valeur null? et poursuit-il? Attendez-vous à écrire une valeur null à la
ObjectOutputStream?
- Nope, c'est juste pour la sécurité - puisque je n'étais pas sûr que ça s'arrête sur
readObject()
et attend jusqu'à ce qu'il y a vraiment quelque chose à y lire. En d'autres mots, j'ai pensé qu'il vérifie et si il ne peut pas lire encore, il retournenull
. - J'étais incertaine de la façon dont vous déployez votre client/serveur. Si vous avez un 1MBit rapport entre eux, alors il sera physiquement prendre environ 2 secondes pour transférer 250KB. Vous pouvez tester la vitesse de simplement récupérer le contenu de votre serveur et de mesurer le temps qu'il faut.
Vous devez vous connecter pour publier un commentaire.
Ces objectifs sont complètement dénuée de sens sans tenir compte de la latence et des bandes passantes de l'intervention du réseau.
Hors sujet. La taille de l'Image est à vous, et il n'a pas de relation réelle, de la programmation, qui est le but de ce site.
Parfaite qualité " exige seulement que vous ne laissez pas tomber tout bits, ce qui vous ne serez pas obtenir de toute façon via TCP.
Qui définit la taille du tampon de réception pour tous les sockets. Définir comme grand que vous pouvez vous permettre, plus de 64 ko si possible.
Comme grand que vous pouvez vous permettre, plus de 64 ko si possible.
Que c'est un socket accepté vous l'avez déjà fait ci-dessus. Retirez.
Les valeurs par défaut pour ces 8k; aussi longtemps que vous avez décent socket tailles de mémoire tampon devrait être suffisant.
Voir ci-dessus.
Ils contrôlent la taille de la TCP "fenêtre". C'est un assez abscons sujet, mais l'idée est d'obtenir la taille au moins égale au produit délai-bande passante de votre réseau, c'est à dire de la bande passante en octets/seconde temps de délai en secondes >= taille de la mémoire tampon en octets.
Ne pas futz autour de personnes avec et de faire d'autres tâches pendant que l'envoi de données. Envoyer aussi vite que vous le pouvez dans le plus serré possible en boucle vous pouvez organiser.
Hors sujet et probablement de l'inconnaissable, à moins qu'un Skype employé qui se passe pour vous révéler les secrets de l'entreprise ici.
Si le client ouvre une nouvelle connexion TCP pour chaque image, puis TCP démarrage lent mai à cause de la lenteur. --> Utiliser la même connexion TCP pour toutes les images.
TCP propres tampons qui sont utilisés de façon optimale pour le protocole TCP -->
BufferedOutputStream
donne parfois bénéficier et quelques fois pas.Habituellement seulement une petite partie de l'écran est modifiée entre les captures d'écran. --> Transfert seulement changé les pièces.
ImageCapsule
(à l'intérieur il contient le tableau d'octets avec l'image encodée) dansObjectOutputStream
qui encapsuleBufferedOutputStream
qui enveloppeGZIPOutputStream
qui, finalement, enveloppements de la priseOutputStream
🙂 Donc, je peuxflush
seulement surObjectOutputStream
. Mais je suppose qu'il en résulte une chaîne deflush
appels respectifs de flux dans l'habillage de la chaîne, n'est-ce pas?Je pense que le plus grand de la bande passante des déchets réside dans le transfert complet de l'ordinateur de bureau. Vous devez traiter ce plus comme un film et de faire du différentiel de codage d'images.
L'inconvénient est la plus complexe de la manipulation. Peut-être il y a quelques simple codec vidéo Api/implémentations là?
Regarder de Java NIO (http://www.cs.brown.edu/courses/cs161/papers/j-nio-ltr.pdf), l'utilisation de plus d'un canal, etc.
La question est claire.
Quelqu'un au-dessus de moi suggère l'utilisation de plusieurs nio canaux, je préfère utiliser plusieurs de blocage de la douille (nio n'est pas plus rapide). Cependant, ce n'est pas nette amélioration de comm, c'est la programmation parallèle, sans aucun doute, serait une bonne solution dans votre cas.
Permettez-moi de suggérer la suite
http://docs.oracle.com/javase/1.4.2/docs/api/java/net/Socket.html#setTcpNoDelay(booléen)
la valeur true, vous serez en mesure de la vitesse de la prise comm jusqu'au prix d'une augmentation de la consommation de la bande passante.