Précédent   Forum du club des développeurs et IT Pro > Autres langages > Langages fonctionnels > Défis langages fonctionnels
Défis langages fonctionnels Divers challenges concernant les langages fonctionnels (lisp, caml, haskell, scheme...)
Partagez cette discussion sur d'autres réseaux sociaux : Viadeo Twitter Google Facebook Digg Delicious MySpace Yahoo
Réponse
 
Outils de la discussion
Publicité
'
Vieux 13/09/2007, 17h29   #1
millie
Rédacteur/Modérateur
 
Avatar de millie
 
Inscription : juin 2006
Messages : 6 935
Détails du profil
Informations personnelles :
Localisation : Luxembourg

Informations forums :
Inscription : juin 2006
Messages : 6 935
Points : 9 062
Points : 9 062
Par défaut Défi N°3 : Réalisation d'un mini-serveur

Bonjour,

Pour ce troisième défi, l'équipe de developpez.com a décidé de faire un challenge délicat à résoudre avec un langage purement fonctionnel, pour montrer que dans des cas particuliers, un langage impératif peut être efficace.

Challenge

Le but du challenge est de réaliser un mini-serveur.
Le serveur utilisera le protocole TCP et attendra des demandes de connexions sur un port quelconque (par exemple 3000).
Lorsqu'un client se connecte, il pourra envoyer plusieurs chaines au serveur :
  • si la chaine est un entier, le serveur retournera au client ce nombre au carré
  • dans le cas contraire, le serveur retournera un message d'erreur

Tant que le client ne se sera pas déconnecté, le serveur devra continuer à être en écoute pour ce client particulier (il est possible de définir un timeout dans le cas où le client n'envoie aucune donnée durant un certain temps).

Le serveur devra pouvoir gérer simultanément plusieurs clients.

Il est possible de tester cette application avec la commande :
  • socket sous Unix (socket localhost 3000 pour se connecter)
  • netcat ou telnet sous Windows (telnet localhost 3000 ou nc localhost 3000)


Exemple

Par exemple, si vous vous connectez avec socket localhost 3000. Voici un scénario de fonctionnement :

Code :
1
2
3
4
5
6
7
Envoie de : 8
Reception de : 64
Envoie de : dfgfdg
Reception de : Erreur
Envoie de : 2
Reception de : 4

Les règles

Il n'y a pas de règle particulière (évidemment, il faut que la solution proposée fonctionne). Vous pouvez proposer des solutions aussi bien dans des langages fonctionnels (caml, haskell, scheme, lisp...) qu'impératif. Le public pourra ainsi juger du code suivant divers critères :
  • la maintenabilité
  • la simplicité
  • le fait qu'il soit optimisé


Le public pourra également juger les différences entre une solution fonctionnelle et une solution impérative. Il lui sera ainsi plus facile de voir, pour un même problème, les différences entre divers paradigmes.


A vos claviers
de votre participation.
__________________
Je ne répondrai à aucune question technique en privé
millie est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 13/09/2007, 18h00   #2
LLB
Membre Expert
 
Inscription : mars 2002
Messages : 962
Détails du profil
Informations forums :
Inscription : mars 2002
Messages : 962
Points : 1 148
Points : 1 148
Citation:
Envoyé par millie Voir le message
Le public pourra également juger les différences entre une solution fonctionnelle et une solution impérative.
J'attends de voir les différentes solutions, mais je pense que ça comparera plus les bibliothèques réseau des langages que les langages en eux-mêmes. Mais ça reste tout aussi intéressant.


Voilà une solution en Shell (testée sous Zsh et Bash). Elle n'est pas tout à fait parfaite, puisqu'elle ne gère qu'un seul client à la fois (le serveur se termine quand le client part). Mais la connexion reste active pour le client et il y a une gestion d'erreur (qui affiche en plus le numéro de la ligne erronée). Elle gère aussi les entiers de n'importe quelle taille.

Code :
nc -lp 3000 -c 'sed -u "s/.*[^0-9].*/*/;s/.*/&*&/" | bc'
En jouant avec les processus, il devrait être possible de gérer plusieurs clients simultanés et de laisser le serveur actif quand le client part (je n'ai pas trouvé d'option pour ça dans netcat).

Exemple d'utilisation (en gras, les réponses du serveur) :
Code :
1
2
3
4
5
6
7
8
9
$ nc localhost 3000
8
64
d
(standard_in) 2: parse error
123456789
15241578750190521
fsdfsd
(standard_in) 4: parse error
J'expliquerai le code si quelqu'un est intéressé (mais je pense que c'est plus enrichissant d'essayer de comprendre par soi-même).


Promis, je proposerai une solution plus "classique". Mais un autre jour.
LLB est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 13/09/2007, 18h43   #3
DrTopos
Membre éclairé

 
Inscription : août 2005
Messages : 417
Détails du profil
Informations personnelles :
Âge : 62

Informations forums :
Inscription : août 2005
Messages : 417
Points : 339
Points : 339
anubis/library/examples/network/client_server.anubis:

(je n'ai pas changé un iota, ce qui fait que ce n'est pas exactement ce qui est demandé, mais presque)

Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
 *Project*                               Anubis   
   
 *Title*                      A simple client/server program. 
   
 *Copyright*                 Copyright (c) Alain Prouté 2005. 
   
 *Released*  
   
 *Author* Alain Prouté
   
   
   *Overview*
   
   This  file contains  a example  of a  server  and the  corresponding client.   It is  a
   'minimalist' program,  as simple as  possible. In  this file you  learn how to  use the
   following functions:
   
      function                               defined in 
      ----------------------------------------------------------------------
      start_server                           predefined.anubis
      remote_IP_address_and_port             predefined.anubis
      ip_addr_to_string                      tools/basis.anubis
      prompt                                 tools/basis.anubis
      reliable_write                         tools/basis.anubis
      tcp                                    tools/connections.anubis
      to_byte_array                          predefined.anubis
      ip_address                             tools/basis.anubis
      connect                                predefined.anubis


   In order to try it out, compile this  file and open two consoles. In the first console,
   issue the command:
   
 simple_server 3000
   
   In the second console, issue the command:
   
 simple_client 127.0.0.1 3000

   Now, enter  some text in the  client (second) console.  It should appear in  the server
   (first) console.
   
   You may start  several clients. Of course, 3000  in this example is the  port number on
   which the  srver is listening.  You may also  start several servers provided  thay have
   distinct listening port numbers.
   
   
   
   
   *** Description. 
   
   The server  starts and waits  for one  or several client  programs to connect.   When a
   client program is connected, the user  of this client program may input sentences using
   the keyboard.  Each line is transmitted to  the server, and the server prints it on its
   console. That's all.
   
   There is nothing to do in order  to handle several clients simultaneously. This is done
   automatically by the functions defined in 'predefined.anubis'.
   
   
read tools/basis.anubis   
read tools/connections.anubis
   
  
   
   *** The server.
   
   The server  needs a 'handler'. Recall  (see 'predefined.anubis') that when  a client is
   connected, the  server starts a virtual  machine for handling this  client (one machine
   per client).  This virtual machine runs  the following program, which  is an 'infinite'
   loop.  Actually, the loop is exited when the client closes the connection.
   
   The  server handler  reads  bytes  from the  connection  until a  line  feed ('\n')  is
   found. Then,  it prints the bytes  prefixed by the address  and port of  the client. It
   continues to read until the connection is closed by the client.
   
define One 
   server_handler
     ( 
       RWStream      connection,      // the connection with the client
       List(Word8)   read_so_far,     // the bytes read so far (in reverse order)
       String        from             // the prefix string (for example: 
                                      // "From 127.0.0.1:12345: ")
     ) =
   if *connection is          // read one byte from the connection
                              // (this expression waits for the next byte or the connection 
                              // is closed by the client)
     {
       failure then           // the connection has been closed by the client
         print(from+"Connection closed by client.\n"),
   
       success(c) then        // the byte 'c' has been read
         if c = '\n'
         // if this byte is '\n', it is time to print the message received from the client
         then (print(from+
                  implode(reverse(read_so_far))+"\n");  // print the message
               server_handler(connection,[],from))      // continue to wait for bytes
   
         // otherwise, just remember the byte read and continue to wait for bytes
         else server_handler(connection,[c . read_so_far],from)
     }.

   
   Notice that  the recursive  calls to  'server_handler' above are  terminal so  that the
   above function  is an actual loop  (the stack of the  virtual machine does  not grow at
   each call).
   
  
   The function 'start_server' requires the handler in the form of a function of type:
   
                              Server -> ((RWStream) -> One)
   
   (see 'predefined.anubis').   The 'Server' argument is  not used by the  handler in this
   example.   The  next  function  'make_server_handler'  is just  an  interface  so  that
   'start_server' can get the handler with the required type.
   
define Server -> ((RWStream) -> One)
   make_server_handler
     (
       One u         // this dummy argument is required because the Anubis compiler
                     // version 1 cannot handle definition of functions at top level
                     // with no argument after the name. This is due to the fact that
                     // version 1 identifies functions of zero operand with the result
                     // of applying this function to zero operand. This was an
                     // unfortunate design idea. 
     ) =
   (Server s) |-> 
   (RWStream connection) |-> 
      //
      // we use 'remote_IP_address_and_port' (see 'predefined.anubis')
      // for getting the address and port of the client. 
      //
      if remote_IP_address_and_port(connection) is (a,p) then 
      print("New connection from "+ip_addr_to_string(a)+":"+p+"\n");
      server_handler(connection,
                     [],
                     //
                     // and we construct the prefix string once and for all
                     //
                     "From "+ip_addr_to_string(a)+":"+p+": "). 

   
   The 'simple_server'  program must  be started  with a port  number. The  function below
   recalls the syntax.
   
define One
   server_syntax
     =
   print("Usage: simple_server <port number>\n"). 
   
   
   Now, here  is the function generating  the module 'simple_server.adm'. We  need to read
   the port number  (which is a string), to  transform it to an integer, and  to start the
   server. All these operations may produce errors, which are handled by error messages.
   
global define One
   simple_server
     (
       List(String) args
     ) =
   if args is 
     {
       [ ] then server_syntax, 
       [h . _] then 
         if string_to_integer(h) is 
           {
             failure then server_syntax, 
             success(port) then 
               if start_server(0,     // listen on all network interfaces
                               port,
                               make_server_handler(unique),
                               (One u) |-> u) is 
                 {
                   cannot_create_the_socket then print("Cannot create the socket for listening.\n"),
                   cannot_bind_to_port      then print("Cannot bind to port "+h+".\n"),
                   cannot_listen_on_port    then print("Cannot listen on port "+h+".\n"),
                   ok(server)               then print("Simple Server started on port "+h+".\n")
                 }
           }
     }.
   
   
   
   
   
   *** The client.
   
   The client  handler sends a  'prompt' to the  client console, and  waits for a  line of
   text.  The  function 'prompt'  used below is  taken from 'tools/basis.anubis'.   We use
   'reliable_write' (also  defined in 'tools/basis.anubis')  for sending the  message. The
   function  'reliable_write'  requires a  connection  of  type  'Connection' (defined  in
   'tools/connections.anubis'). This is why the  constructor 'tcp' is required here. Also,
   the  text must  be transformed  into a  byte array  before being  sent.  The conversion
   function 'to_byte_array' is defined in 'predefined.anubis'. 
   
define One
   client_handler
     (
       RWStream connection
     ) =
   with text = prompt("simple_client> "), 
   if reliable_write(tcp(connection),to_byte_array(text+"\n")) is 
     {
       failure     then print("Cannot send message.\n"),
       success(n)  then unique
     }; 
   client_handler(connection). 
   
   Notice again that the above is an 'infinite' loop. Actually, this loop cannot be exited
   within the program.  It  is exited only when 'anbexec' (of the  client) is stopped by a
   [Ctrl  C].   Also  notice that  we  must  not  forget  to  send "\n"  appended  to  the
   text. Otherwise, the  server will wait indefinitely  for the end of the  line, and will
   print nothing. This "\n" is part of our 'simple transmission protocol'. 
   
   
   The client program requires  an IP address and port number in order  to be able to find
   the server. The next function recalls the syntax of 'simple_client'.
   
define One
   client_syntax
     (
       String additional_message
     ) =
   print("Usage: simple_client <IP address> <port number>\n");
   print(additional_message). 
   
   
   Now, here is the function  generating the module 'simple_client.adm'. The arguments (IP
   address and port number) are first read and converted. 
   
global define One
   simple_client
     (
       List(String) args
     ) =
   if args is 
     {
       [ ] then   // no argument on command line 
         client_syntax("Address and port number missing.\n"),
   
       [a . t] then if t is 
         {
           [ ] then // only one argument on command line
             client_syntax("Port number missing.\n"),
   
           [p . _] then // try to convert the IP address 
                        // 'ip_address' is defined in 'tools/basis.anubis'
             if ip_address(a) is 
               {
                 failure then 
                   client_syntax("Incorrect IP address.\n"),
   
                 success(addr) then // try to convert the port number
                   if string_to_integer(p) is 
                     {
                       failure then 
                         client_syntax("Incorrect port number.\n"),
   
                       success(port) then // try to connect to server
                         if connect(addr,port) is
                           {
                             error(msg) then 
                               print("Cannot connect to "+a+":"+p+"\n"),
   
                             ok(connection) then 
                               //
                               // connection to server is OK
                               // just run the client handler
                               // 
                               print("Connected to "+a+":"+p+"\n"); 
                               client_handler(connection)
                           }
                     }
               } 
         }
     }.
Jusqu'au numéro de port qui est le même !

Pour l'exécuter, il faut une version 1.8. Vous en trouverez une ici pour Linux.
__________________
Ma page maths.
DrTopos est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 13/09/2007, 19h06   #4
DrTopos
Membre éclairé

 
Inscription : août 2005
Messages : 417
Détails du profil
Informations personnelles :
Âge : 62

Informations forums :
Inscription : août 2005
Messages : 417
Points : 339
Points : 339
Citation:
Envoyé par LLB Voir le message
Code :
nc -lp 3000 -c 'sed -u "s/.*[^0-9].*/*/;s/.*/&*&/" | bc'
Est-ce qu'il faut mettre ce code dans un fichier de nom nc rendu executable ?
__________________
Ma page maths.
DrTopos est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 13/09/2007, 19h27   #5
gorgonite
Rédacteur/Modérateur

 
Avatar de gorgonite
 
Homme Nicolas Vallée
Ingénieur d'études
Inscription : décembre 2005
Messages : 9 963
Détails du profil
Informations personnelles :
Nom : Homme Nicolas Vallée
Âge : 28
Localisation : France

Informations professionnelles :
Activité : Ingénieur d'études
Secteur : Transports

Informations forums :
Inscription : décembre 2005
Messages : 9 963
Points : 18 157
Points : 18 157
Citation:
Envoyé par DrTopos Voir le message
Est-ce qu'il faut mettre ce code dans un fichier de nom nc rendu executable ?
taper le code dans une console...
__________________
Evitez les MP pour les questions techniques... il y a des forums
Contributions sur DVP : Mes Tutos | Mon Blog
gorgonite est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 13/09/2007, 19h51   #6
DrTopos
Membre éclairé

 
Inscription : août 2005
Messages : 417
Détails du profil
Informations personnelles :
Âge : 62

Informations forums :
Inscription : août 2005
Messages : 417
Points : 339
Points : 339
Heu ... La ligne de code shell proposée par LLB me fait swapper ma LinuxBox (Ubuntu Dapper Drake) que même la souris ne bouge plus (j'ai essayé deux fois).
__________________
Ma page maths.
DrTopos est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 13/09/2007, 22h05   #7
LLB
Membre Expert
 
Inscription : mars 2002
Messages : 962
Détails du profil
Informations forums :
Inscription : mars 2002
Messages : 962
Points : 1 148
Points : 1 148
J'ai essayé sur plusieurs machines (Linux, FreeBSD), en local comme en réseau, et ça a bien marché. Sous FreeBSD, netcat n'avait pas l'option -c, j'ai dû utiliser l'option -e (et le gnu sed s'appelle gsed) :
Code :
1
2
3
4
$ cat a.sh
#! /bin/sh
gsed -u "s/.*[^0-9].*/*/;s/.*/&*&/" | bc
$ nc -lp 3000 -e ./a.sh
Je ne vois pas comment ça peut planter... au pire, il ne connait pas une option ou ne trouve pas un binaire. Normalement, il y a juste à exécuter le code. Le serveur attend ensuite qu'un client se connecte.
LLB est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 13/09/2007, 23h35   #8
LLB
Membre Expert
 
Inscription : mars 2002
Messages : 962
Détails du profil
Informations forums :
Inscription : mars 2002
Messages : 962
Points : 1 148
Points : 1 148
Voilà, la gestion multi-clients est faite.

Je viens d'écrire cette version, avec une communication entre les processus via un tube nommé :

Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#! /bin/bash

server() {
    nc -lp 3000 -c "echo Client logged on > $lock; "'sed -u "s/.*[^0-9].*/*/;s/.*/&*&/" | bc'
    echo "Client logged off"
}

lock=$(mktemp -p /tmp/)
rm -f $lock && mkfifo $lock
server &
while true; do
    tail -n 1 $lock
    server &
done

... avant de me rendre compte qu'on peut faire bien plus simple, via une récursion mutuelle (server.sh et nc) :
Code server.sh :
1
2
3
#! /bin/bash
nc -lp 3000 -e server.sh &
sed -u "s/.*[^0-9].*/*/;s/.*/&*&/" | bc
Appel (dans le shell) :
Code shell :
$ nc -lp 3000 -e server.sh

La première version a le mérite d'être plus verbeuse à l'exécution (côté serveur, j'affiche quand un client se connecte ou se déconnecte). La deuxième, bien plus courte, utilise un processus de moins (celui qui faisait le while true).
LLB est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 14/09/2007, 13h24   #9
LLB
Membre Expert
 
Inscription : mars 2002
Messages : 962
Détails du profil
Informations forums :
Inscription : mars 2002
Messages : 962
Points : 1 148
Points : 1 148
Voilà maintenant une solution basique en F#.

C'est la première fois que j'utilise du réseau et des threads sous .NET. Je me suis donc inspiré de codes en C#. Pas grand-chose de particulier à dire, je crée un thread à chaque connexion.

Code F# :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#light

open System.Net
open System.Net.Sockets
open System.Text
open System.Threading

let rec process (client: TcpClient) (stream: NetworkStream) =
  let bytes = Array.create 100 0uy
  stream.Read(bytes, 0, 100) |> ignore
  let data = Encoding.ASCII.GetString(bytes)
  let res =
    try int_of_string data |> (fun x -> x * x) |> sprintf "%d\n"
    with _ -> "Invalid number.\n"
  stream.Write(Encoding.ASCII.GetBytes(res), 0, res.Length)
  process client stream

let run client () =
  try process client (client.GetStream())
  with _ ->
    client.Close()
    printf "Client logged out\n"

let () =
  try
    let server = TcpListener(IPAddress.Parse("127.0.0.1"), 3000)
    server.Start()
    while true do
      let client = server.AcceptTcpClient()
      printf "Client logged in\n"
      Thread(ThreadStart(run client)).Start()
  with e -> printf "Error: %s\n" e.Message
J'ai testé sous Windows, via nc.

Pour reprendre ce que je disais, je ne pense pas qu'un langage à dominante impérative apporte vraiment, c'est surtout une question de bibliothèque. D'ailleurs, cette version en F# est plus concise que les codes C# que j'ai trouvés, quand bien même la bibliothèque réseau aurait été pensée en impératif.
LLB est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 14/09/2007, 18h16   #10
gorgonite
Rédacteur/Modérateur

 
Avatar de gorgonite
 
Homme Nicolas Vallée
Ingénieur d'études
Inscription : décembre 2005
Messages : 9 963
Détails du profil
Informations personnelles :
Nom : Homme Nicolas Vallée
Âge : 28
Localisation : France

Informations professionnelles :
Activité : Ingénieur d'études
Secteur : Transports

Informations forums :
Inscription : décembre 2005
Messages : 9 963
Points : 18 157
Points : 18 157
une petite version OCaml minimaliste...


Code ocaml :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
open Unix;;

let launch_server address port max_connections =
   let sock = socket PF_INET SOCK_STREAM 0 in
   let addr = inet_addr_of_string address in
   let _ = bind sock (ADDR_INET(addr,port)) in
   let _ = listen sock max_connections in
   sock
;;

let process_server sock buffer_size =
   let process client_sock =
      let split str = 
         try
            (String.index str '\n')-1
         with Not_found -> (String.length str)-1
      in
      try 
         let buf = String.create buffer_size in
         let _ = recv client_sock buf 0 buffer_size [] in
         let len = split buf in
         let str = String.sub buf 0 len in 
(*         let _ = Printf.printf "%s\t%d\n%!" str (String.length str) in *)
         let n = int_of_string str in
         let _ = Printf.printf "%d\n%!" n in
         let tmp = string_of_int (n*n) in
         let msg = String.concat "" [tmp;"\n"] in
         let _ = send client_sock msg 0 (String.length msg) [] in
         close client_sock 
      with Failure("int_of_string") -> ( Printf.printf "error\n%!" ; 
                                        let _ = send client_sock "erreur\n\n%!" 0 7  [] in 
                                        close client_sock )
   in
   let rec aux () = 
         let (client_sock,client_addr) = accept sock in
         match fork() with
               0 -> process client_sock ; exit 0
             | id -> ( Unix.close client_sock ; aux () )
   in
   aux () 
;;

let main () =
   let _ =  Printf.printf "Program starting...\n%!" in
   let sock = launch_server "127.0.0.1" 3000 10 in
   process_server sock 10
;;


main ();;
__________________
Evitez les MP pour les questions techniques... il y a des forums
Contributions sur DVP : Mes Tutos | Mon Blog
gorgonite est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 14/09/2007, 19h15   #11
gasche
Membre Expert
 
Inscription : avril 2007
Messages : 829
Détails du profil
Informations forums :
Inscription : avril 2007
Messages : 829
Points : 1 007
Points : 1 007
Minimaliste ? peut mieux faire

Code :
1
2
3
4
5
6
7
8
9
10
open Printf
open Unix

let rec server input output =
  fprintf output "%s\n%!"
    (try Scanf.fscanf input " %d" (fun x -> string_of_int (x * x))
     with _ -> "erreur");
  server input output

let () = establish_server server (ADDR_INET (inet_addr_loopback, 3000))
gasche est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 15/09/2007, 10h17   #12
millie
Rédacteur/Modérateur
 
Avatar de millie
 
Inscription : juin 2006
Messages : 6 935
Détails du profil
Informations personnelles :
Localisation : Luxembourg

Informations forums :
Inscription : juin 2006
Messages : 6 935
Points : 9 062
Points : 9 062
Voici la solution que j'avais fait, et je crois que niveau taille, je suis largement battu.

Source en java, il y a un timeout au cas où le client ne parle plus pendant 5 secondes. Ca gère plusieurs clients jusqu'à leur déconnexion (ou timeout).

Code java :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import java.net.ServerSocket;
import java.lang.Thread;
import java.net.Socket;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Integer;
import java.io.BufferedReader;
import java.io.InputStreamReader;
 
class Client extends Thread {
  private Socket _s;
 
  public Client(Socket s) {
    _s = s;
  }
 
  public void run() {
    try {
      //on recupère les flux pour la lecture et l'écriture
      InputStream iStream = _s.getInputStream();
      OutputStream oStream = _s.getOutputStream();
      BufferedReader reader = new BufferedReader(new InputStreamReader(iStream));
 
      String g;
 
      //lecture d'une ligne. Non bloquant car soTimeout mis
      while((g = reader.readLine()) != null) {
        String retour = "Error";
 
        try {
          int i = Integer.parseInt(g); //conversion du nombre
          i *= i; //calcul du carré
          retour = Integer.toString(i); //détermination de la chaine à retourner
        }   
        catch(Exception e) {
          //si une exception est lancée, c'est que le nombre n'est pas convertible
          System.err.println("Client2 " + e.getMessage());
        }
 
        oStream.write(retour.getBytes()); //envoi de la chaine
        oStream.flush();
      }
 
 
    }
    catch(Exception e) {
      System.err.println("Client " + e.getMessage());
    }
    finally {
      try {
        _s.close(); //on ferme le socket
      }
      catch(Exception e) {
      }
    }
  }
} 
 
public class Defi {
 
  public static void main(String[] args) {
    ServerSocket serverSocket = null;
    try {
      serverSocket = new ServerSocket(3000); //création du serveur pour les connections
    }
    catch(Exception e) {
      System.err.println(e.getCause());
      return;
    }
 
    while(true) {
      Socket s = null;
 
      try {
        s = serverSocket.accept(); //attente d'un nouveau client
        s.setSoTimeout(5000); //timeout à 5 secondes pour éviter les surcharges
 
        Client c = new Client(s);
        c.start(); //démarrage du thread
      }
      catch(Exception e) {
        System.err.println("Main" + e.getMessage());
        return;
      }
 
    }
 
 
  }
}
__________________
Je ne répondrai à aucune question technique en privé
millie est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 15/09/2007, 23h19   #13
Woufeil
Rédacteur
 
Avatar de Woufeil
 
Étudiant
Inscription : février 2006
Messages : 1 076
Détails du profil
Informations personnelles :
Âge : 25
Localisation : France, Haute Garonne (Midi Pyrénées)

Informations professionnelles :
Activité : Étudiant

Informations forums :
Inscription : février 2006
Messages : 1 076
Points : 2 080
Points : 2 080
Ah, enfin un défi pile dans le domaine d'application de Perl

Une solution simple pour commencer (mais qui a tout de même la particularité de créer un processus par client) :

Code Perl :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#! /usr/bin/perl -w
use strict;
use IO::Socket::INET;
 
my $serveur =  IO::Socket::INET->new ( LocalPort => 3000, Type => SOCK_STREAM, Reuse => 1, Listen => 5) or die "Problème serveur : $!";
my $chaine;
while (my $client = $serveur->accept()) {
	my $pid = fork;
	unless ($pid) {
		while (1) {
			print $client "Entrez le nombre dont vous voulez connaître le carré : ";
			$chaine = <$client>;
			exit unless defined $chaine;
			chomp ($chaine);
			if ($chaine =~ m/^\d+\r?$/) {
				my $rep = "Le carré de $chaine vaut ".$chaine * $chaine;
				print $client "$rep\n";
			} 	
			else {
				print $client "Erreur !\n";
			}
		}
	}
}

Alors Jedai, qu'est ce qui ne vas pas ?
__________________
"En essayant continuellement, on finit par réussir. Donc : plus ça rate, plus on a de chances que ça marche" (devise Shadock)
Application :

ainsi qu'à regarder la avant de poser une question.

La rubrique Perl recrute, contactez-moi.
Woufeil est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 19/09/2007, 11h47   #14
Taum
Membre chevronné
 
Inscription : mai 2005
Messages : 657
Détails du profil
Informations forums :
Inscription : mai 2005
Messages : 657
Points : 726
Points : 726
Une solution rapide en Ruby, qui ressemble étrangement à celle en Perl
Elle gère également plusieurs clients en parallèle.

Code :
1
2
3
4
5
6
7
8
9
10
11
12
#!/opt/local/bin/ruby

require 'socket'

server = TCPServer.new('0.0.0.0', 3000)
while(stream = server.accept)
  fork do
    while(input = stream.gets)
      stream.print(input =~ /^\d+$/ ? "#{input.to_i**2}\n" : "#{input.chomp} n'est pas un entier!\n")
    end
  end  
end
Ca faisait un moment que je n'avais pas utilisé while en Ruby, mais il me semble que pour ce challenge le style impératif est effectivement plus adapté
__________________
Toute la documentation Ruby on Rails : gotapi.com/rubyrails
Mes articles :
> HAML : langage de template pour Ruby on Rails
Taum est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 31/12/2007, 01h43   #15
Jedai
Expert Confirmé Sénior
 
Avatar de Jedai
 
Étudiant
Inscription : avril 2003
Messages : 6 068
Détails du profil
Informations personnelles :
Localisation : France, Rhône (Rhône Alpes)

Informations professionnelles :
Activité : Étudiant

Informations forums :
Inscription : avril 2003
Messages : 6 068
Points : 8 209
Points : 8 209
Envoyer un message via Yahoo à Jedai
Citation:
Envoyé par Taum Voir le message
Ca faisait un moment que je n'avais pas utilisé while en Ruby, mais il me semble que pour ce challenge le style impératif est effectivement plus adapté
C'est à dire que c'est purement de l'IO, donc pas vraiment le domaine du fonctionnel. Ce qui ne veut pas dire que les langages fonctionnels y sont vraiment mauvais, ils n'ont juste pas énormément d'avantages par rapport aux langages impératifs. Ce qui fait vraiment la différence c'est les bibliothèques.

Une petite solution en Haskell :
Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Data.List
import Network
import Control.Exception
import Control.Monad
import Control.Concurrent
import System.IO

main = createServer $ lineByLine square

createServer f = withSocketsDo $ do
                   socket <- listenOn $ PortNumber 3000
                   forever $ do
                     (h,_,_) <- accept socket
                     forkIO $ try (f h `finally` hClose h) >> return ()
      
lineByLine f h = forever $ do
                   line <- hGetLine h
                   hPutStr h $ f (line \\ "\r\n") ++ "\r\n"
                   hFlush h

square "quit" = error "End of this instance"
square line = case reads line of
                (n,""):_ -> line ++ " squared is : " ++ show (n*n)
                _ -> "Error : " ++ line ++ " is not a number"
Ce serveur crée un thread "poid-plume" par client (et distribue ces threads sur des threads réels si on est sur un système multicore).

--
Jedaï
Jedai est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 09/04/2008, 20h09   #16
LLB
Membre Expert
 
Inscription : mars 2002
Messages : 962
Détails du profil
Informations forums :
Inscription : mars 2002
Messages : 962
Points : 1 148
Points : 1 148
Autre solution en F#. La première était inspirée de code C#, celle-là est beaucoup plus typique de F#. Elle repose sur la programmation asynchrone (donc elle ne bloque pas les threads) et les monades. Contrairement à l'autre, elle ne crée par un thread par client et devrait être beaucoup plus légère.

Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#light

open Control.CommonExtensions
open System.Net
open System.Net.Sockets
open System.Text

// Extension de méthodes pour rendre les méthodes .NET plus agréables dans F#.
type System.Net.Sockets.NetworkStream with
    member ns.WriteStringAsync (str: string) =
        ns.WriteAsync(Encoding.ASCII.GetBytes(str), 0, String.length str)

    member ns.ReadStringAsync =
        async { let bytes = Array.create 256 0uy
                let! n = ns.ReadAsync(bytes, 0, 256)
                return Encoding.ASCII.GetString(bytes, 0, n) }

// Fonction de serveur générique (inspirée de la fonction Caml)
let rec establish_server f port =
    let server = TcpListener(IPAddress.Any, port)
    server.Start()
    while true do
        let client = server.AcceptTcpClient()
        printfn "New client"
        Async.Run (Async.SpawnChild (f (client.GetStream())))

// Gestion d'un client de façon asynchrone
let rec server (stream: NetworkStream) =
  async { try
            let! resp = stream.ReadStringAsync
            let res = try int resp |> fun x -> x * x |> string with _ -> "Invalid number."
            do! stream.WriteStringAsync res
            do! server stream
          with _ -> do printfn "Connection closed" }

establish_server server 3000
LLB est déconnecté   Envoyer un message privé Réponse avec citation 00
Vieux 10/04/2008, 10h51   #17
Twinside
Membre du Club
 
Inscription : octobre 2007
Messages : 34
Détails du profil
Informations forums :
Inscription : octobre 2007
Messages : 34
Points : 43
Points : 43
Une solutioin en Erlang. Donc ça lance une thread par client, et je doit valider moi même l'entrée, la fonction standard de la librairie ne retournant pas assez d'information pour se baser dessus.

Code :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
-module( challenge ).

-export([
            launch/1
            ,secure_int/1
            ,clienter/1
        ]).


%
% fonction à lancer.
%
launch( Port ) ->
    IPort = list_to_integer( Port ),
    {ok, LSock} = gen_tcp:listen( IPort,[list] ),
    listener( LSock ).

% thread du serveur, fonction recursive
% infinie.
listener( LSock ) ->
    case gen_tcp:accept( LSock ) of
        {ok, Cli} -> Pid = spawn(?MODULE, clienter, [Cli] ),
                    gen_tcp:controlling_process( Cli, Pid );
        _ -> io:format( "Error can't accept a client~n" )
    end,
    listener( LSock ).

secure_int( Str ) -> intaux( 0, Str ).

% Convertie une chaine en entier de manière
% sécurisé et s'arretant en fin de ligne, si
% existe
intaux( Val, []         ) -> Val;
intaux( Val, [$\n | _]  ) -> Val;   % on autorise les fin de ligne
intaux( Val, [$\r | _]  ) -> Val;   % on autorise les fin de ligne windows
intaux( Val, [V | Next] )
    when (V >= $0) and (V =< $9) -> intaux( Val * 10 + V - $0, Next );
intaux( _, _ ) -> error.

%
% Thread du client.
%
clienter( Sock ) ->
    receive
        {tcp, Sock, Data} -> perform_client_logic( Sock, Data ),
                             clienter( Sock );
        {tcp_closed, _Sock} -> io:format( "Fin de connection~n" ),
                                ok;              
        _ -> io:format( "unknown command~n" ),
             clienter( Sock )
    end.

perform_client_logic( Sock, Data ) ->
    case secure_int( Data ) of
        error -> gen_tcp:send( Sock,  "Erreur\n" );
        Val -> Msg = integer_to_list( Val * Val ) ++ "\n",
                gen_tcp:send( Sock, Msg )
    end.
Twinside est déconnecté   Envoyer un message privé Réponse avec citation 00
Réponse
Outils de la discussion

Navigation rapide


Fuseau horaire GMT +2. Il est actuellement 10h02.


 
 
 
 
Partenaires

Hébergement Web