A CRUD journey in Haskell, part II, Socket programming

A CRUD journey in Haskell, part II, Socket programming

A simple TCP server written in Haskell

Assuming that we've got some introduction to Haskell, let's start doing some Socket programming in order to build a simple TCP server.

Requirements

For the purpose of this article, I'll expect that you have some knowledge in client-server architecture, basic networking protocols and TCP/IP.

You can get a deeper understanding on what's a client-server architecture by reading this article, where we explained some networking fundamentals and created a simple TCP server in Ruby).

TCP server

First things first, we're going to use the network hackage package which exposes the module Network.Socket that provides full control over TCP sockets.

Server.hs

import Network.Socket

main :: IO ()
main = do
  ...

This will pretty much provide some essential functions for Socket programming:

  • sock: creates a new socket given the address family, socket type and protocol number
  • bind: binds the socket to an address
  • listen: listen for connections on the socket specifying the max number of queued connections
  • accept: accepts a connection and returns a connection socket
  • socketToHandle: turns a connection socket into a handle, ready to be read/written

However, the module Network.Socket is not enough. It does not provide functions to manipulate the handle. Then, we have to import the module System.IO that comes with the base hackage package.

import Network.Socket
import System.IO

main :: IO ()
main = do
  ...

The System.IO module provides a lot of functions to manipulate I/O, but for now we will need:

  • hGetLine: reads a line from the handle
  • hPutStrLn: writes the String to the handle
  • hClose: closes the handle

Once we presented all the needed functions for our simple TCP server, let's dig into its implementation.

Setup and accepting connections

First, let's create a new socket:

sock <- socket AF_INET Stream 0

Bind the socket to the port 4000:

bind sock (SockAddrInet 4000 0)

Listen for connections:

listen sock 2

And, last but not least, accept a connection:

-- the server will keep blocked on this line until a new TCP connection arrives
(conn, _) <- accept sock

The accept function returns a pair (conn, address), but at this moment we will ignore the client address and use only the conn socket

Now we have our server ready to accept incoming TCP connections. But yet we can't read/write messages through the socket.

Manupulating the client socket

Turn a socket into a handle on read/write mode:

handleSock <- socketToHandle conn ReadWriteMode

Read a line from the handle (read request):

line <- hGetLine handleSock
putStrLn $ "Request received: " ++ line

Send some response back to the handle, of course:

hPutStrLn handleSock $ "Hey, client!"

Now, we can safely close that client socket connection:

hClose handleSock

The full implementation

Our TCP server is so simple that its implementation looks like this:

import Network.Socket                            
import System.IO                                 

main :: IO ()                                    
main = do                                        
  sock <- socket AF_INET Stream 0                
  bind sock (SockAddrInet 4000 0)                
  listen sock 2                                  
  putStrLn "Listening on port 4000..."           

  (conn, _) <- accept sock                       
  putStrLn "New connection accepted"             

  handleSock <- socketToHandle conn ReadWriteMode

  line <- hGetLine handleSock                    
  putStrLn $ "Request received: " ++ line        

  hPutStrLn handleSock $ "Hey, client!"          
  hClose handleSock

In order to test the TCP server, let's use a simple TCP client in Ruby that will connect to the server and send a message:

client.rb

require 'socket'                              

server = TCPSocket.open('localhost', 4000)

server.puts 'Hey Server!'                     

response = ''                                 

while line = server.gets                      
  response += line                            
end                                           

puts "Response received: #{response}"         

server.close

Start the server:

ghc app/Server.hs -e main

# Listening on port 4000...

Run the client:

ruby client.rb

Server output:

Listening on port 4000...    
New connection accepted      
Request received: Hey Server!

Client output:

Response received: Hey, client!

A note on TCP client-server architecture

Note that our TCP server accepts a new connection, reads/writes to the socket, then closes the connection, exiting the program.

A more reliable server should return back to the listening connections after every client socket is closed. It means that it must loop forever.

In functional programming, we can loop over a function using recursion:

loopForever sock = do                            
  (conn, _) <- accept sock                       
  handleSock <- socketToHandle conn ReadWriteMode

  line <- hGetLine handleSock                    
  putStrLn $ "Request received: " ++ line        

  hPutStrLn handleSock $ "Hey, client!"          
  hClose handleSock                              
  loopForever sock
  • We moved all the code related to a specific client connection to a new function called loopForever
  • In the last line, we call the function recursively

Final implementation:

main :: IO ()                                    
main = do                                        
  sock <- socket AF_INET Stream 0                
  bind sock (SockAddrInet 4000 0)                
  listen sock 2                                  
  putStrLn "Listening on port 4000..."           

  loopForever sock                               

loopForever :: Socket -> IO ()                   
loopForever sock = do                            
  (conn, _) <- accept sock                       
  handleSock <- socketToHandle conn ReadWriteMode

  line <- hGetLine handleSock                    
  putStrLn $ "Request received: " ++ line        

  hPutStrLn handleSock $ "Hey, client!"          
  hClose handleSock                              
  loopForever sock

As for now, our TCP server won't exit after closing each client connection. Long live the Server! 🎉

Conclusion

In this article we learned how to do some basic Socket programming in Haskell.

This lesson will be the ground for the upcoming posts, which will cover a more sophisticated kind of message over TCP: the Hypertext Transfer Protocol (HTTP).

 
Share this