Saturday, March 3, 2007

Simple Socket Programming

I think one of the things that made perl so popular was the abundance of fantastic documentation about how to do stuff. I've been fooling with Haskell recently, the approach to documentation is different. For example, if you want to write a simple haskell server, you'll likely find this example: A simple TCP server <edit> In addition to the simple tcp server take a look at Stephan Walter's interact for TCP. It's a good trick for getting single threaded servers working quickly.</edit>


That's a server, but if you're as clueless as I am, it's a bit intimidating. You need to know about sockets. You need to know about exceptions. You need to know about handles. You need to know about concurrency. You need to know about software transactional memory. I wanted something with a shallower learning curve.

I've used the bind syntax rather than do syntax. I feel the do syntax is great sugar, but gets in my when way when trying to understand at the basic level. So, without further ado, a very simple server:

>import Network
>import System.IO
>import Control.Exception
>import Control.Concurrent
>
>oneShot = listenOn (PortNumber 8000) >>=
> \sock -> accept sock >>= talk >> sClose sock
> where talk (handle, name, port)
> = hPutStrLn handle "hi!" >> hClose handle

One shot in english: First, we create a server socket with listenOn. Second, we use that socket to accept and use a connection, Finally we close the connection. The talk function talks to the client. Talk gets a handle, we can use any of the System.IO h functions for client communication. Reading and writing works using the handle. Capturing the socket in the lambda \sock -> allows closing the socket at the end of the run.

Here's the same program again, with do syntax:

>oneShot2 = do
> sock <- listenOn (PortNumber 8000)
> conn <- accept sock
> talk conn
> sClose sock
> where talk (h, n, p) = do
> hPutStrLn h "hi!"
> hClose h

Only a handful functions need to be considered. listenOn, accept and sClose are all from the Network library. hPutStrLn and hClose come from System.IO. there's good stuff in those libraries, worth a look.

oneShot only accepts a single connection, then exits. Version two can handle many clients


>manyShot = bracket
> (listenOn $ PortNumber 8000)
> (sClose)
> (loop)
> where loop sock = accept sock >>= talk >> loop sock
> talk (h, n, p) = hPutStrLn h "hi!" >> hClose h


Two big changes between oneShot and manyShot. First, looping over the accept lets manyShot handle client after client.

Second is the use of exception handling. bracket takes three arguments, how to initialize, how to clean up, and what work to actually do. If there's an exception in loop, bracket will take care of cleaning up the server socket.

The next problem to resolve is threading. Currently only one client at a time can be handled.


>threaded = bracket
> (listenOn $ PortNumber 8000)
> (sClose)
> (loop)
> where loop sock = accept sock >>= handle >> loop sock
> handle (h, n, p) =
> forkIO (hPutStrLn h "hi!" >> hClose h)


forkIO takes a computation and runs it in another thread. This version can handle simultaneous requests.

A few important notes. I ran all of these examples in ghci in Aquamacs on OS X. I should have included withSocketsDo but i don't run windows. be sure to check that out if you're interested in developing cross platform servers.

Also, if you're compiling at every step, you will need a line like this:


>main = oneShot

to test the individual servers. compile with ghc --make for maximum simplicity.

Jason

4 comments:

Anonymous said...

Hi,

I also thought that the "simple TCP server" example was not that simple, but if you don't need concurrency, writing Haskell servers is really easy. I just wrote "interact " for TCP. Works just like "interact" but uses TCP sockets instead of stdin/stdout. My version does not support multiple connections though.

Unknown said...

I saw interact for TCP on reddit the other day. It's a good trick. Mostly, this post was a learning exercise. I understand quite a bit better now that I laid sockets out in the simplest terms i could manage. Next up is a simple chat server. i don't think i'll support IRC, more of a mud style communication.

Thanks for the note,
Jason

nyx said...

Hey,

I'm very new to Haskell myself, (just started yesterday), and I'm also thinking of attempting to write a simple MUD server as a learning exercise. Your article was helpful as a basic introduction to how sockets work in Haskell; it seems much less intimidating than the C equivalent :p

diegoeche said...

Thank you!
Im new o haskell and your article was really helpful.

:D