Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell: How to use forkIO to enable multiple clients to connect to a server?

I'm trying to get multiple clients to connect to a server. What I've managed to do is connect one client to a server by using for the server:

main = withSocketsDo $ do 
          socket                 <- listenOn port
          (handle, host, portno) <- accept socket
          hSetBuffering handle LineBuffering
          msg <- hGetLine handle
          putStrLn $ "The client says: " ++ msg
          hClose handle
          sClose socket
          putStrLn "Server is done."

and for the client:

main = withSocketsDo $ do
  handle <- connectTo "localhost" port
  hSetBuffering handle LineBuffering
  hPutStrLn handle "Hello!"
  hClose handle

These are clearly just for testing purposes ;)

Now, I've read that I need to use forkIO to enable multiple clients to connect to this one server. However I haven't been able to find how I should use forkIO or how to manage the several clients that will connect. Can someone explain to me what I should do?

Thanks in advance!

like image 347
user1034455 Avatar asked Nov 07 '11 20:11

user1034455


2 Answers

The key is that once you've accepted a connection using accept, you'll want to fork a new thread to handle the connection while the main thread goes back to listening. So something like this should do the trick.

main = withSocketsDo $ do 
          socket <- listenOn port
          -- We want to accept multiple connections,
          -- so we need to accept in a loop
          forever $ do 
              (handle, host, portno) <- accept socket
              -- But once we've accepted, we want to go off and handle the
              -- connection in a separate thread
              forkIO $ do
                  hSetBuffering handle LineBuffering
                  msg <- hGetLine handle
                  putStrLn $ "The client says: " ++ msg
                  hClose handle

Note that this way the server keeps running until you kill the process, which is common behavior for many servers. Implementing more graceful shutdown will require some cross-thread communication using MVars or STM.

like image 117
hammar Avatar answered Oct 23 '22 11:10

hammar


Just as a general style comment, I'd split the reaction of the server to the client's connection into a separate function. This makes it easier to read in my opinion

main = withSocketsDo $ do
    socket <- listenOn port
    accept socket >>= handleClientRequest socket

handleClientRequest socket (handle, host, portno) = do
      hSetBuffering handle LineBuffering
      msg <- hGetLine handle
      putStrLn $ "The client says: " ++ msg
      hClose handle
      sClose socket
      putStrLn "Server is done."

now we probably want it to loop indefinitely, as that tends to be how most servers work. So we use forever (from Control.Monad)

main = withSocketsDo $ do
    socket <- listenOn port
    forever $ accept socket >>= handleClientRequest socket

handleClientRequest socket (handle, host, portno) = do
      hSetBuffering handle LineBuffering
      msg <- hGetLine handle
      putStrLn $ "The client says: " ++ msg
      hClose handle
      sClose socket
      putStrLn "Server is done."

and from here, the manner in which to use forkIO becomes quite clear

main = withSocketsDo $ do
    socket <- listenOn port
    forever $ accept socket >>= forkIO . (handleClientRequest socket)

handleClientRequest socket (handle, host, portno) = do
      hSetBuffering handle LineBuffering
      msg <- hGetLine handle
      putStrLn $ "The client says: " ++ msg
      hClose handle
      sClose socket
      putStrLn "Server is done."
like image 7
Probie Avatar answered Oct 23 '22 13:10

Probie