Haskell Network Programming - TCP Client and Server
Using the network package we can build a low level TCP server and client. We will make a simple echo server and client. The client sends a message, the server receives the message and sends the message back to the client, then the client receives the message it sent.
import Control.Concurrent (forkIO, threadDelay)
import Control.Monad (unless)
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as C
import Network.Socket hiding (recv)
import Network.Socket.ByteString (recv, sendAll)
runTCPEchoServer :: IO ()
runTCPEchoServer = do
addrinfos <- getAddrInfo Nothing (Just "127.0.0.1") (Just "7000")
let serveraddr = head addrinfosIt is important to remember to use Stream to receive
data and respond via TCP. Then we can bind the socket to the address,
wait to receive data and then respond to the client.
sock <- socket (addrFamily serveraddr) Stream defaultProtocol
bind sock (addrAddress serveraddr)
listen sock 1
(conn, _) <- accept sock
print "TCP server is waiting for a message..."
msg <- recv conn 1024
unless (BS.null msg) $ do
print ("TCP server received: " ++ C.unpack msg)
print "TCP server is now sending a message to the client"
sendAll conn msg
print "TCP server socket is closing now."
close conn
close sockThe client code is similar. We need to sned data via
Datagram or the server will ignore it. Then we run
sendAll with a ByteString and the server will
receive the message.
sendMessage :: String -> IO ()
sendMessage s = do
addrinfos <- getAddrInfo Nothing (Just "127.0.0.1") (Just "7000")
let serveraddr = head addrinfos
sock <- socket (addrFamily serveraddr) Stream defaultProtocol
connect sock (addrAddress serveraddr)
sendAll sock $ C.pack s
msg <- recv sock 1024
close sock
-- delay thread to avoid client and server from printing at the same time
threadDelay 1000000
print ("TCP client received: " ++ C.unpack msg)We run the server in a separate thread because recv is
blocking. We add a few threadDelays to make sure that the
server has started up and that the prints from different
threads do not occur at the same time. Otherwise, the messages might
print at the same time and be illegible.
main :: IO ()
main = do
_ <- forkIO $ runTCPEchoServer
threadDelay 1000000 -- wait one second
sendMessage "Hello, world!"
threadDelay 1000000 -- wait one secondWhen main terminates all of the other threads in the
program will terminate as well [2].
A real world TCP server will likely need to constantly receive requests and make responses. We can make the request/response code an infinite loop.
runTCPEchoServerForever :: IO ()
runTCPEchoServerForever = do
addrinfos <- getAddrInfo Nothing (Just "127.0.0.1") (Just "7000")
let serveraddr = head addrinfos
sock <- socket (addrFamily serveraddr) Stream defaultProtocol
bind sock (addrAddress serveraddr)
listen sock 1
(conn, _) <- accept sock
print "TCP server is waiting for a message..."
rrLoop conn
print "TCP server socket is closing now."
close conn
close sock
where
rrLoop conn = do
msg <- recv conn 1024
unless (BS.null msg) $ do
print ("TCP server received: " ++ C.unpack msg)
print "TCP server is now sending a message to the client"
sendAll conn msg