OVH Cloud OVH Cloud

Java et la programmation réseau

7 réponses
Avatar
Julien Arlandis
Quelles sont les performances du java dans un environnement serveur?
Je viens de coder un serveur NNTP, il fonctionne bien, mais il est
extraordinairement lent lorsqu'il s'agit de récupérer des articles avec
pièces jointes. Pour vous donner un ordre d'idées, en localhost il me
faut plusieurs minutes pour poster un fichier de 1 MO mais 2 secondes
seulement pour un fichier texte de 60 ko. Le serveur tourne sur mon
portable cadencé à 2,4 Ghz.


Voici la partie de code (dans les grandes lignes) que j'aimerais optimiser :

public class connexion extends Thread {
Socket client;
DataInputStream C;
PrintStream S;

public connexion(Socket client) {
this.client = client;
C = new DataInputStream(client.getInputStream());
S = new PrintStream(client.getOutputStream());
[...]
new Thread(this).start();
}

public void run() {
String line, result;

while ((line = C.readLine()) != null)
{
resultat = Appel_ma_métode_NNTP(line);
[...]
S.print(resultat);
}
}


Il semblerait que ce soit l'instruction C.readLine() qui ralentit la
transmission. Mon code peut-il être encore optimisé?

7 réponses

Avatar
narberd
Julien Arlandis wrote:
Quelles sont les performances du java dans un environnement serveur?
Je viens de coder un serveur NNTP, il fonctionne bien, mais il est
extraordinairement lent lorsqu'il s'agit de récupérer des articles avec
pièces jointes. Pour vous donner un ordre d'idées, en localhost il me
faut plusieurs minutes pour poster un fichier de 1 MO mais 2 secondes
seulement pour un fichier texte de 60 ko. Le serveur tourne sur mon
portable cadencé à 2,4 Ghz.


Voici la partie de code (dans les grandes lignes) que j'aimerais
optimiser :

public class connexion extends Thread {
Socket client;
DataInputStream C;
PrintStream S;

public connexion(Socket client) {
this.client = client;
C = new DataInputStream(client.getInputStream());
S = new PrintStream(client.getOutputStream());
[...]
new Thread(this).start();
}

public void run() {
String line, result;

while ((line = C.readLine()) != null)
{
resultat = Appel_ma_métode_NNTP(line);
[...]
S.print(resultat);
}
}


Il semblerait que ce soit l'instruction C.readLine() qui ralentit la
transmission. Mon code peut-il être encore optimisé?


J'utilise beaucoup les sockets, pour un volume de donnée très importa nt
chez bon nombre de clients. Mes problèmes de performances sont plus liè s
à la configuration du réseau qu'au code Java lui même.

Tout d'abord, j'utilise beaucoup de threads - ce que tu fais apparament.
Ensuite, je n'utilise JAMAIS readLine, mais je lis les
données en binaire. Ca n'a l'air de rien mais que fait readLine() ? Il
lit les octets un par un jusqu'à la détection du code de fin de ligne .
Chez moi, pour jouer, j'ai fait un - petit - serveur HTTP en Lisp. J'ai
abandonné l'équivalent Lisp de readLine() et je travaille directement
sur les octets que j'interprète.

Pratiquement, je dis au socket qu'il doit bufferiser sur 64 Ko - que ce
soit en Java ou en Lisp, cela me donne de bonne performance mais
peut-être que ton appli à besoin d'une autre valeur. Ensuite, je lis
caractère par caractère. Dans le cas du serveur HTTP, la détection de
CRLF me donne la fin de la ligne que j'envoie à un processus (thread,
c'est en Lisp) qui fait le traitement. Mon thread principal continue la
lecture des données jusqu'à la détection de fin de fichier (le sock et
est fermé par l'appli en face) ou d'un time-out.

Voilà donc une solution - mais il y en a peut-être d'autres.

Bon courage,

Narberd.

Avatar
Christophe M
Julien Arlandis wrote:
Quelles sont les performances du java dans un environnement serveur?
Je viens de coder un serveur NNTP, il fonctionne bien, mais il est
extraordinairement lent lorsqu'il s'agit de récupérer des articles avec
pièces jointes. Pour vous donner un ordre d'idées, en localhost il me
faut plusieurs minutes pour poster un fichier de 1 MO mais 2 secondes
seulement pour un fichier texte de 60 ko. Le serveur tourne sur mon
portable cadencé à 2,4 Ghz.


Voici la partie de code (dans les grandes lignes) que j'aimerais
optimiser :

public class connexion extends Thread {
Socket client;
DataInputStream C;
PrintStream S;

public connexion(Socket client) {
this.client = client;
C = new DataInputStream(client.getInputStream());
S = new PrintStream(client.getOutputStream());
[...]
new Thread(this).start();
}

public void run() {
String line, result;

while ((line = C.readLine()) != null)
{
resultat = Appel_ma_métode_NNTP(line);
[...]
S.print(resultat);
}
}


Il semblerait que ce soit l'instruction C.readLine() qui ralentit la
transmission. Mon code peut-il être encore optimisé?


Essaye en plaçant un buffer entre le socket et ton readline :

C = new DataInputStream(new
BufferedInputStream(client.getInputStream()));

Sinon y a de bons bouquins sur le sujet :
http://www.amazon.fr/exec/obidos/ASIN/2841771342/402-5632604-7482502

voir même déjà une application qui gère un serveur NNTP :
http://james.apache.org/ (ok il en fait un petit peu plus...)

Avatar
Julien Arlandis
J'utilise beaucoup les sockets, pour un volume de donnée très important
chez bon nombre de clients. Mes problèmes de performances sont plus liès
à la configuration du réseau qu'au code Java lui même.

Tout d'abord, j'utilise beaucoup de threads - ce que tu fais apparament.
Ensuite, je n'utilise JAMAIS readLine, mais je lis les
données en binaire. Ca n'a l'air de rien mais que fait readLine() ? Il
lit les octets un par un jusqu'à la détection du code de fin de ligne.
Chez moi, pour jouer, j'ai fait un - petit - serveur HTTP en Lisp. J'ai
abandonné l'équivalent Lisp de readLine() et je travaille directement
sur les octets que j'interprète.

Pratiquement, je dis au socket qu'il doit bufferiser sur 64 Ko - que ce
soit en Java ou en Lisp, cela me donne de bonne performance mais
peut-être que ton appli à besoin d'une autre valeur. Ensuite, je lis
caractère par caractère. Dans le cas du serveur HTTP, la détection de
CRLF me donne la fin de la ligne que j'envoie à un processus (thread,
c'est en Lisp) qui fait le traitement.


Pas bête, c'est peut être ça qui fait défaut dans mon code, j'ouvre un
thread pour chaque socket crée à la connection d'un nouveau client,
ensuite je gère le stream à l'intérieur du thread.


Mon thread principal continue la
lecture des données jusqu'à la détection de fin de fichier (le socket
est fermé par l'appli en face) ou d'un time-out.


Et que se passe t-il si le client envoie une courte commande qui ne
dépasse pas 64 ko? Le serveur va attendre que le tampon se remplisse
pour répondre au client?
Dans le cas d'un serveur NNTP chaque commande est validée par un "rn"
ou par un "rn.rn" (pour poster un article) et exige une réponse
immédiate, même si je mets un tout petit buffer de 16 octets, je ne
résoudrai pas le problème pour un faible trafic. Si le client tape la
commande "HELP", il aura rempli le tampon de 6 octets seulement.



Voilà donc une solution - mais il y en a peut-être d'autres.

Bon courage,

Narberd.



Avatar
narberd
Julien Arlandis wrote:
Pratiquement, je dis au socket qu'il doit bufferiser sur 64 Ko - que
ce soit en Java ou en Lisp, cela me donne de bonne performance mais
peut-être que ton appli à besoin d'une autre valeur. Ensuite, je l is
caractère par caractère. Dans le cas du serveur HTTP, la détect ion de
CRLF me donne la fin de la ligne que j'envoie à un processus (thread ,
c'est en Lisp) qui fait le traitement.



Pas bête, c'est peut être ça qui fait défaut dans mon code, j'o uvre un
thread pour chaque socket crée à la connection d'un nouveau client,
ensuite je gère le stream à l'intérieur du thread.


Mon thread principal continue la

lecture des données jusqu'à la détection de fin de fichier (le s ocket
est fermé par l'appli en face) ou d'un time-out.



Et que se passe t-il si le client envoie une courte commande qui ne
dépasse pas 64 ko? Le serveur va attendre que le tampon se remplisse
pour répondre au client?
Dans le cas d'un serveur NNTP chaque commande est validée par un "r n"
ou par un "rn.rn" (pour poster un article) et exige une réponse
immédiate, même si je mets un tout petit buffer de 16 octets, je ne
résoudrai pas le problème pour un faible trafic. Si le client tape la
commande "HELP", il aura rempli le tampon de 6 octets seulement.


Le thread de lecture doit faire une détection de fin de ligne.

De manière plus précise, mon serveur HTTP en Lisp qui traite bien des
lignes - mes autres applis traitent vraiment du binaire - a trois
threads par connexions. Le principal qui lit les données caractère pa r
caractère. Lorsqu'une fin de ligne est détectée, il la rajoute à une
liste de lignes et réveille un autre thread qui vide cette liste de
lignes. Le troisième thread gère l'écriture sur le socket. Ma machi ne
étant lente (233 MHz et 196 Mo de Ram lente), j'ai pu multiplier par 2. 5
les temps de traitement.

En fait, on peut certainement utiliser readLine. L'avantage de ma
méthode est que je fait un peu plus que la détection de fin de ligne. Je
décode en partie le contenu à la volée. Le protocole HTTP, c'est
essaentielement quelque chose comme :
COMMANDE le reste
et donc je découpe déjà la ligne en deux parties. Cela aussi doit
modifier singulièrement la vitesse de traitement.

Un autre point : mon tableau de lignes est vidé régulièrement, je n e me
retrouve jamais (j'ai bien testé) avec plus de 20 ou 30 lignes en
mémoire en même temps. Si ton process de vidage de la liste est trop
lent, la liste augmente, bouffe de la mémoire et l'application est vite
pénalisée - les temps d'allocation augmente sérieusement lorsqu'on
commence à swapper. Au début, j'avais des problèmes de synchronisat ion
de process qui m'engorgeait la mémoire et faisait tout écrouler.

Maintenant, je suis à peu près aussi performant que mon serveur Apach e -
mais avec moins de possibilités quand même - pour environ 15-20
connexions. Au delà, Apache est plus fort que moi, car il a une gestion
de connexions plus sophistiquée que la mienne, une méthode de mise en
cache des pages très plus mieux et plein d'autres trucs.

Salut,

Narberd.


Voilà donc une solution - mais il y en a peut-être d'autres.

Bon courage,

Narberd.





Avatar
Julien Arlandis
Et que se passe t-il si le client envoie une courte commande qui ne
dépasse pas 64 ko? Le serveur va attendre que le tampon se remplisse
pour répondre au client?
Dans le cas d'un serveur NNTP chaque commande est validée par un
"rn" ou par un "rn.rn" (pour poster un article) et exige une
réponse immédiate, même si je mets un tout petit buffer de 16 octets,
je ne résoudrai pas le problème pour un faible trafic. Si le client
tape la commande "HELP", il aura rempli le tampon de 6 octets seulement.



Le thread de lecture doit faire une détection de fin de ligne.

De manière plus précise, mon serveur HTTP en Lisp qui traite bien des
lignes - mes autres applis traitent vraiment du binaire - a trois
threads par connexions. Le principal qui lit les données caractère par
caractère. Lorsqu'une fin de ligne est détectée, il la rajoute à une
liste de lignes et réveille un autre thread qui vide cette liste de
lignes. Le troisième thread gère l'écriture sur le socket. Ma machine
étant lente (233 MHz et 196 Mo de Ram lente), j'ai pu multiplier par 2.5
les temps de traitement.

En fait, on peut certainement utiliser readLine. L'avantage de ma
méthode est que je fait un peu plus que la détection de fin de ligne. Je
décode en partie le contenu à la volée. Le protocole HTTP, c'est
essaentielement quelque chose comme :
COMMANDE le reste
et donc je découpe déjà la ligne en deux parties. Cela aussi doit
modifier singulièrement la vitesse de traitement.

Un autre point : mon tableau de lignes est vidé régulièrement, je ne me
retrouve jamais (j'ai bien testé) avec plus de 20 ou 30 lignes en
mémoire en même temps. Si ton process de vidage de la liste est trop
lent, la liste augmente, bouffe de la mémoire et l'application est vite
pénalisée - les temps d'allocation augmente sérieusement lorsqu'on
commence à swapper. Au début, j'avais des problèmes de synchronisation
de process qui m'engorgeait la mémoire et faisait tout écrouler.

Maintenant, je suis à peu près aussi performant que mon serveur Apache -
mais avec moins de possibilités quand même - pour environ 15-20
connexions. Au delà, Apache est plus fort que moi, car il a une gestion
de connexions plus sophistiquée que la mienne, une méthode de mise en
cache des pages très plus mieux et plein d'autres trucs.


Comment vous associez les commandes au bon socket?

Admettons que César se connecte et tape en telnet les commandes suivantes :
Commande1
Commande2
Commande3

En même temps Hercule est connecté et tape :
Commande4
Commande5
Commande6

Votre tableau de lignes contiendra une liste telle :
Commande1
Commande2
Commande4
Commande5
Commande3
Commande6

Commande rendre à César ce qui lui appartient?


Avatar
Julien Arlandis
En fait, on peut certainement utiliser readLine. L'avantage de ma
méthode est que je fait un peu plus que la détection de fin de ligne. Je
décode en partie le contenu à la volée. Le protocole HTTP, c'est
essaentielement quelque chose comme :
COMMANDE le reste
et donc je découpe déjà la ligne en deux parties. Cela aussi doit
modifier singulièrement la vitesse de traitement.

Un autre point : mon tableau de lignes est vidé régulièrement, je ne me
retrouve jamais (j'ai bien testé) avec plus de 20 ou 30 lignes en
mémoire en même temps. Si ton process de vidage de la liste est trop
lent, la liste augmente, bouffe de la mémoire et l'application est vite
pénalisée - les temps d'allocation augmente sérieusement lorsqu'on
commence à swapper. Au début, j'avais des problèmes de synchronisation
de process qui m'engorgeait la mémoire et faisait tout écrouler.


J'ai enfin trouvé ce qui ralentissait mon serveur!
Dans la boucle principale j'utilisais une variable de type String pour
stocker les requêtes (String contenu_article += chaine_lue;) en virant
cette simple instruction je suis environ 5000 fois plus rapide !!!!!!!
Du coup il me faut trouver une autre méthode pour stocker mes articles
en mémoires, je vais passer par des tableaux... à moins qu'il y ait une
solution plus simple?

Avatar
oliv
Julien Arlandis wrote:
Dans la boucle principale j'utilisais une variable de type String pour
stocker les requêtes (String contenu_article += chaine_lue;) en virant
cette simple instruction je suis environ 5000 fois plus rapide !!!!!!!
Comme les String sont immutables, cette concaténation exige chaque

fois une nouvelle allocation. Si tu utilises un StringBuffer avec
append() j'imagine que tu garderas une fonctionnalité équivalente mais
sans ce problème d'alloc.

--
oliv