L01man
L01man

Reputation: 1523

Game server in Haskell

I'm using Network and Gloss for a game server in Haskell. It works fine, except that the client has to close for the server to receive the data it sent. I bet it's a case of laziness...

Minimalist server:

import Network
import System.IO

main = do
    sock <- listenOn (PortNumber (fromIntegral 12345))
    loop sock

loop sock = do
    (hIn, _, _) <- accept sock
    str <- hGetContents hIn
    print str
    loop sock

Minimalist client:

import Network
import System.IO
import Graphics.Gloss.Interface.IO.Game

main = playIO
    (InWindow "Test Multi" (500, 500) (500, 500))
    white
    60
    Nothing
    draw
    (\_ x -> return x)
    advance

draw Nothing = return blank
draw (Just x) = return (Text (show x))

advance _ Nothing = do
    hOut <- connectTo "000.000.0.0" (PortNumber (fromIntegral 12345))
    hSetBuffering hOut NoBuffering
    hPutStr hOut "Hello!"
    return (Just hOut)
advance _ x = return x

I start the server, wait 10 seconds, then start the client, wait 15 seconds, see that nothing happens on the server, closes the client, see "Hello!" suddenly appear on the server. I would like "Hello!" to appear while the client is running, in an advance call, otherwise I can't make a multiplayer game (sob)!


However, if I change the client's code to

main = loop Nothing
loop x = do
    x' <- advance 0 x
    getLine

the sever immediatly shows "Hello!" while the client is waiting for my input.


I tried, as suggested in another question, to use bang patterns and hClose:

-- ...
!str <- hGetContents hIn
hClose hIn
-- ...

which makes the output appear immediatly, without the client closing. That's great. But, I plan to use bytestrings because the data I send to the server is serialized, so I import qualified Data.ByteString as B and change hGetContents to B.hGetContents, which makes the problem re-appear.


The problem was indeed a case of laziness. hGetContents reads lazily all the contents of the Handle, so it finishes only when it's closed, when the client aborts the connection. Instead, I used hGetLine that returns the content each time it encounters a \n, which I use as a end-of-message tag.

Upvotes: 13

Views: 2259

Answers (3)

AndrewC
AndrewC

Reputation: 32455

I might be completely wrong, but isn't the problem hGetContents? Surely that should wait till the entire contents sent through your socket have arrived before the next line (print...) starts. hGetContents is designed to give you all the contents sent until the socket is closed. Something like hGetLine could terminate straight away and you can leave the socket open to send more data through later. Your client could then use a hPutStrLn instead of hPutStr.

Upvotes: 8

Ilya Rezvov
Ilya Rezvov

Reputation: 944

Probably, you need to disable algorithm Nagle. Try this code:

import Network.Socket

setSocketOption sock NoDelay 1

Upvotes: 2

Gabriella Gonzalez
Gabriella Gonzalez

Reputation: 35089

It defaults to line-buffered output, which is why hPutStr (which doesn't provide a line ending) doesn't output anything until you flush the buffer. There are two ways you can solve this:

a) Call hFlush stdout manually any time you want to flush the output.

b) Use hSetBuffering to set the buffering to NoBuffering

All of those functions are found in the System.IO module.

Edit: Never mind, I just saw where you did that in the client. I retract my answer with apologies.

Upvotes: 3

Related Questions