IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Défis langages fonctionnels Discussion :

Défi N°3 : Réalisation d'un mini-serveur


Sujet :

Défis langages fonctionnels

  1. #1
    Rédacteur

    Avatar de millie
    Profil pro
    Inscrit en
    juin 2006
    Messages
    7 013
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : juin 2006
    Messages : 7 013
    Points : 9 698
    Points
    9 698
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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é

  2. #2
    LLB
    LLB est déconnecté
    Membre expérimenté
    Inscrit en
    mars 2002
    Messages
    967
    Détails du profil
    Informations forums :
    Inscription : mars 2002
    Messages : 967
    Points : 1 339
    Points
    1 339
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  3. #3
    Membre averti
    Profil pro
    Inscrit en
    août 2005
    Messages
    417
    Détails du profil
    Informations personnelles :
    Âge : 70
    Localisation : France

    Informations forums :
    Inscription : août 2005
    Messages : 417
    Points : 372
    Points
    372
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  4. #4
    Membre averti
    Profil pro
    Inscrit en
    août 2005
    Messages
    417
    Détails du profil
    Informations personnelles :
    Âge : 70
    Localisation : France

    Informations forums :
    Inscription : août 2005
    Messages : 417
    Points : 372
    Points
    372
    Par défaut
    Citation Envoyé par LLB Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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 ?

  5. #5
    Rédacteur/Modérateur

    Avatar de gorgonite
    Homme Profil pro
    Ingénieur d'études
    Inscrit en
    décembre 2005
    Messages
    10 321
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

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

    Informations forums :
    Inscription : décembre 2005
    Messages : 10 321
    Points : 18 320
    Points
    18 320
    Par défaut
    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

  6. #6
    Membre averti
    Profil pro
    Inscrit en
    août 2005
    Messages
    417
    Détails du profil
    Informations personnelles :
    Âge : 70
    Localisation : France

    Informations forums :
    Inscription : août 2005
    Messages : 417
    Points : 372
    Points
    372
    Par défaut
    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).

  7. #7
    LLB
    LLB est déconnecté
    Membre expérimenté
    Inscrit en
    mars 2002
    Messages
    967
    Détails du profil
    Informations forums :
    Inscription : mars 2002
    Messages : 967
    Points : 1 339
    Points
    1 339
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  8. #8
    LLB
    LLB est déconnecté
    Membre expérimenté
    Inscrit en
    mars 2002
    Messages
    967
    Détails du profil
    Informations forums :
    Inscription : mars 2002
    Messages : 967
    Points : 1 339
    Points
    1 339
    Par défaut
    Voilà, la gestion multi-clients est faite.

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

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    #! /bin/bash
    nc -lp 3000 -e server.sh &
    sed -u "s/.*[^0-9].*/*/;s/.*/&*&/" | bc
    Appel (dans le shell) :
    Code shell : Sélectionner tout - Visualiser dans une fenêtre à part
    $ 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).

  9. #9
    LLB
    LLB est déconnecté
    Membre expérimenté
    Inscrit en
    mars 2002
    Messages
    967
    Détails du profil
    Informations forums :
    Inscription : mars 2002
    Messages : 967
    Points : 1 339
    Points
    1 339
    Par défaut
    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# : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  10. #10
    Rédacteur/Modérateur

    Avatar de gorgonite
    Homme Profil pro
    Ingénieur d'études
    Inscrit en
    décembre 2005
    Messages
    10 321
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : France

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

    Informations forums :
    Inscription : décembre 2005
    Messages : 10 321
    Points : 18 320
    Points
    18 320
    Par défaut
    une petite version OCaml minimaliste...


    Code ocaml : Sélectionner tout - Visualiser dans une fenêtre à part
    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

  11. #11
    Membre éprouvé
    Profil pro
    Inscrit en
    avril 2007
    Messages
    832
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : avril 2007
    Messages : 832
    Points : 1 104
    Points
    1 104
    Par défaut
    Minimaliste ? peut mieux faire

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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))

  12. #12
    Rédacteur

    Avatar de millie
    Profil pro
    Inscrit en
    juin 2006
    Messages
    7 013
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : juin 2006
    Messages : 7 013
    Points : 9 698
    Points
    9 698
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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é

  13. #13
    Membre chevronné
    Avatar de Woufeil
    Profil pro
    Étudiant
    Inscrit en
    février 2006
    Messages
    1 076
    Détails du profil
    Informations personnelles :
    Âge : 33
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : février 2006
    Messages : 1 076
    Points : 2 004
    Points
    2 004
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

  14. #14
    Membre éclairé

    Profil pro
    Inscrit en
    mai 2005
    Messages
    657
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : mai 2005
    Messages : 657
    Points : 895
    Points
    895
    Par défaut
    Une solution rapide en Ruby, qui ressemble étrangement à celle en Perl
    Elle gère également plusieurs clients en parallèle.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    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

  15. #15
    Expert éminent
    Avatar de Jedai
    Homme Profil pro
    Enseignant
    Inscrit en
    avril 2003
    Messages
    6 243
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Côte d'Or (Bourgogne)

    Informations professionnelles :
    Activité : Enseignant

    Informations forums :
    Inscription : avril 2003
    Messages : 6 243
    Points : 8 554
    Points
    8 554
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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ï

  16. #16
    LLB
    LLB est déconnecté
    Membre expérimenté
    Inscrit en
    mars 2002
    Messages
    967
    Détails du profil
    Informations forums :
    Inscription : mars 2002
    Messages : 967
    Points : 1 339
    Points
    1 339
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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

  17. #17
    Membre du Club
    Profil pro
    Inscrit en
    octobre 2007
    Messages
    36
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : octobre 2007
    Messages : 36
    Points : 54
    Points
    54
    Par défaut
    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 : Sélectionner tout - Visualiser dans une fenêtre à part
    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.

Discussions similaires

  1. Défi N°2 : Réalisation d'un interpréteur d'expression mathématiques
    Par millie dans le forum Défis langages fonctionnels
    Réponses: 22
    Dernier message: 13/01/2011, 15h15
  2. Réaliser un mini serveur web
    Par Invité dans le forum API standards et tierces
    Réponses: 10
    Dernier message: 02/11/2009, 18h24
  3. Réaliser un mini serveur web
    Par Invité dans le forum Langage
    Réponses: 1
    Dernier message: 02/11/2009, 15h51
  4. Réaliser une routine coté serveur
    Par capeth dans le forum Langage
    Réponses: 2
    Dernier message: 16/09/2006, 22h13
  5. Réponses: 2
    Dernier message: 07/06/2006, 13h12

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo