HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Simple TCP client - memory issues

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
simpleissuestcpclientmemory

Problem

Does this snippet take too much memory? How can I let the connection opened with the function connectTo outside the forever loop?

import Control.Concurrent
import Network
import System.IO
import Control.Monad
import System.Environment

main = do 
    [host,port]>= hPutStrLn h 
        forkIO $ hGetContents h >>= putStrL


If I put the line forever ... after the line hSetBuffering, the connection is lost after one input.

It seems to me that this code open too much connection.

Solution

hGetContents gets all remaining input, using lazy IO. The second call to hGetContents throws an error because the first call has already claimed the data, in a sense.

What I would do is have a separate thread tunnel data from the handle to standard output, and have the main thread tunnel data from standard input to the handle:

_ >= putStr
getContents >>= hPutStr h


I would also use LineBuffering rather than NoBuffering, so it doesn't have to transmit a TCP packet for every character (*).

Thus, we have:

import Control.Concurrent
import Network
import System.Environment
import System.IO

main :: IO ()
main = do
    [host, port] >= putStr
    getContents >>= hPutStr h


A couple notes:

-
I was able to avoid the type signature by using toEnum rather than fromIntegral.

-
_

We can do better, though. Currently, if the server disconnects, the user does not see that the server disconnected until hitting enter a couple times, which produces an ugly error message:

tcp-client: : commitBuffer: resource vanished (Broken pipe)


Let's see if we can get the program to terminate when either the server or the client closes the connection. Bear in mind that:

-
getContents and hGetContents terminate the list when EOF is reached. Thus:

-
hGetContents h >>= putStr terminates when the server closes the connection

-
getContents >>= hPutStr h` terminates when the user presses Ctrl+D

-
The program terminates when the main thread terminates, regardless if child threads still have work to do.

A good way to do this, I think, is to perform the receiving and sending in two separate threads, and have the main thread wait on an MVar:

done >= putStr)
              `finally` tryPutMVar done ()

_ >= hPutStr h)
              `finally` tryPutMVar done ()

-- Wait for at least one of the above threads to complete
takeMVar done


* Actually, sending individual characters at a time will probably trigger Nagle's algorithm. Still, sending characters one at a time creates a lot of unnecessary CPU overhead.

Code Snippets

_ <- forkIO $ hGetContents h >>= putStr
getContents >>= hPutStr h
import Control.Concurrent
import Network
import System.Environment
import System.IO

main :: IO ()
main = do
    [host, port] <- getArgs
    h <- connectTo host $ PortNumber $ toEnum $ read port
    hSetBuffering stdout LineBuffering
    hSetBuffering h      LineBuffering
    _ <- forkIO $ hGetContents h >>= putStr
    getContents >>= hPutStr h
tcp-client: <socket: 3>: commitBuffer: resource vanished (Broken pipe)
done <- newEmptyMVar

_ <- forkIO $ (hGetContents h >>= putStr)
              `finally` tryPutMVar done ()

_ <- forkIO $ (getContents >>= hPutStr h)
              `finally` tryPutMVar done ()

-- Wait for at least one of the above threads to complete
takeMVar done

Context

StackExchange Code Review Q#8828, answer score: 6

Revisions (0)

No revisions yet.