FrontPage FindPage TitleIndex RecentChanges UserPreferences E D R S I H C
 
Haskell Server Programming

1 Introduction

This tutorial is about server programming in concurrent Haskell. Although, Simon Marlows' web server written in Haskell [1] is a good reference, tutorial for newbies driven by simple example is needed. It is a great pleasure to program servers in applicative languages supporting concurrency. Erlang is an excellent example which had been highly successful in the industry. Erlang's message passing sylte concurrency support is really awesome. You should try it if you ever had interest in server programming. Some haskell implementaions including ghc and hugs support concurrency via lightweighed thread in the runtime system. In GHC's Control.Concurrent module there is a shared mutexed variable type MVar. Though MVar is not so high level as Erlang's message, you can still enjoy the power of modern pure functional language handling concurrencies in server programming elegantly.

The aim of this introductory tutorial is to introduce how to program servers in Haskell with concurrency support. Therefore extensions or libraries beyond the scope of Haskell 98 is limited except for concurency control and high level network libraries. The readers of this document should clearly notice that the example server program codes are far from production quality. Since the example codes provided in this tutorial only uses the high level network libraries rather than using lower level IO and socket libraries, the input and output can be inefficient and can suffer from client abuse. [2] However the example is composed with care to escape from common pitfalls of concurrent programming such as busy wating or unnecessary blocking from misused mutex.

2 Network Basics

3 Concurrency Basics

4 Echo Server

Only 10 lines except import declarations! Echo server is the most simple server, which appears as the first example of most server programming tutorials. Echo server does not share any information between clients. Therefore we do not need any information sharing among threads.

import IO
import Control.Exception hiding (catch)
import Control.Concurrent
import Network
import System.Posix

main = withSocketsDo (installHandler sigPIPE Ignore Nothing >> main')
main' = listenOn (PortNumber 9900) >>= acceptConnections

acceptConnections sock = do
        putStrLn "trying to accept" -- debug msg
        conn@(h,host,port) <- accept sock
        print conn -- debug msg
        forkIO $ catch (talk conn `finally` hClose h) (\e -> print e)
        acceptConnections sock

talk conn@(h,_,_) = hGetLine h >>= hPutStrLn h >> hFlush h >> talk conn

Running example
sever console
kyagrd@kyagrd:hsserv$ ./echo.bin
trying to accept
({handle: <socket: 4>},"220.85.155.11",49223)
trying to accept
<socket: 4>: hGetLine: end of file

client console
kyagrd@kyagrd:kyagrd$ telnet kyagrd.dyndns.org 9900
Trying 220.85.155.11...
Connected to kyagrd.dyndns.org.
Escape character is '^]'.
abcd
abcd
asdf1234
asdf1234
^]
telnet> quit
Connection closed.
kyagrd@kyagrd:kyagrd$
This also works fine with multiple clients.

5 Chat Server

Chat server is much more complicated than echo server. Chat server should get messages from the client and broadcast the messages to all of the clients. As a result there should be sharing or passing of some information among the client threads. These informations are list of connected client and the collected messages to broadcast. These two informations are implemented as mutex variables. In addition there is a broadcaster thread. Another mutex variable works as a semaphore [3] to avoid busy wating of the broadcaster.

import IO
import Control.Exception hiding (catch)
import Control.Concurrent
import Network
import System.Posix
import List
import Monad

main = withSocketsDo (installHandler sigPIPE Ignore Nothing >> main')
main' = do
    sock <- listenOn (PortNumber 9901)
    mvH <- newMVar [] -- list of client handles
    mvM <- newMVar [] -- messags stack ... not queue :)
    mvS <- newEmptyMVar -- semaphore
    let mvt = (mvH,mvM,mvS)
    forkIO $ broadcast mvt `finally` putMVar mvS ()
    acceptConnections sock mvt

acceptConnections sock mvt@(mvH,mvM,_ ) = do
    print "trying to accept" -- for debugging message
    conn@(h,host,port) <- accept sock
    print conn -- for debugging message
    modifyMVar_ mvH (return . (h:))
    forkIO $ catch (talk conn mvt `finally` closeConn h mvH) print
    acceptConnections sock mvt

talk conn@(h,host,port) mvt@(_ ,mvM,mvS) =
    hGetLine h >>= stackMsg >> talk conn mvt
    where stackMsg m = do
        modifyMVar_ mvM (return . ((show (host,port)++':':m):))
        tryPutMVar mvS () -- V

closeConn h mvH = modifyMVar_ mvH (return . delete h) >> hClose h

broadcast mvt@(mvH,mvM,mvS) = do
    takeMVar mvS -- P
    ms <- swapMVar mvM []
    mapM_ (\m ->mapM_ (failSafe(\h->hPutStrLn h m>>hFlush h))=<<readMVar mvH) ms
    broadcast mvt
    where failSafe ioWithHarg h = catch (ioWithHarg h) print

Running example
server console
kyagrd@kyagrd:hsserv$ ./chat.bin
"trying to accept"
({handle: <socket: 4>},"220.85.155.11",49500)
"trying to accept"
<socket: 4>: hGetLine: end of file

clent console
kyagrd@kyagrd:kyagrd$ telnet kyagrd.dyndns.org 9901
Trying 220.85.155.11...
Connected to kyagrd.dyndns.org.
Escape character is '^]'.
asdf
("220.85.155.11",49500):asdf
asdf1234
("220.85.155.11",49500):asdf1234
^]
telnet> quit
Connection closed.
kyagrd@kyagrd:kyagrd$
This also works fine with multiple clients.

6 Suggestions for further studies

  • Impove performance and reliability by replacing basic IO with lower level sockets with timeout and non blocking features.
  • Mimick Erlang's message passing style by throwing dynamic typed exceptions to other threads.

7 Download source codes and makefile

@hsserv.tar.gz (1.05 KB)

This was tested under my debian sarge liux box using ghc6 package.
kyagrd@kyagrd:hsserv$ ls
chat.hs  echo.hs  makefile
kyagrd@kyagrd:hsserv$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.2.1
kyagrd@kyagrd:hsserv$ make
ghc --make echo.hs -o echo.bin -package network -package posix
Chasing modules from: echo.hs
Compiling Main             ( echo.hs, echo.o )
Linking ...
ghc --make chat.hs -o chat.bin -package network -package posix
Chasing modules from: chat.hs
Compiling Main             ( chat.hs, chat.o )
Linking ...
kyagrd@kyagrd:hsserv$ ls
chat.bin*  chat.hs  echo.bin*  echo.hs  makefile
chat.hi    chat.o   echo.hi    echo.o
kyagrd@kyagrd:hsserv$ ./echo.bin
trying to accept
echo.bin: internal error: main thread has been GC'd
    Please report this as a bug to glasgow-haskell-bugs@haskell.org,
    or http://www.sourceforge.net/projects/ghc/
kyagrd@kyagrd:hsserv$ ./chat.bin
"trying to accept"
chat.bin: internal error: main thread has been GC'd
    Please report this as a bug to glasgow-haskell-bugs@haskell.org,
    or http://www.sourceforge.net/projects/ghc/
kyagrd@kyagrd:hsserv$ make clean
rm -f echo.bin chat.bin *.hi *.o
kyagrd@kyagrd:hsserv$ ls
chat.hs  echo.hs  makefile
The error internal error message comes out when pressing Ctrl-C.

8 Document History

  • First made this page. -- kyagrd 2005-01-05

----
  • [1] http://www.haskell.org/haskellwiki/Applications There is a link to Simon Marlow's web server related information.
  • [2] e.g. Client can block some server thread by not handling the server's response. The socket handle does not have timeout.
  • [3] The simplest bistate semaphore. GHC concurrnet library also provides more general semaphore implementations.


<!> You may add comments here.

acceptConnections can raise exception because of accept or any other system signal. (Thanks to Simon Peyton Jones's posting in haskell-cafe list) -- kyagrd 2005-01-05

last modified 2009-03-09 13:46:45
EditTextFindPageDeletePageLikePagesUploadedFiles