Игровой сервер в Haskell

Я использую Network и Gloss для игрового сервера в Haskell. Он отлично работает, за исключением того, что клиент должен закрыть сервер для получения отправленных данных. Держу пари, это случай лени...

Минималистский сервер:

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

Минималистический клиент:

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

Я запустил сервер, подождал 10 секунд, затем запустил клиент, подождал 15 секунд, посмотрел, что на сервере ничего не происходит, закрывает клиент, см. "Привет!" . неожиданно появляются на сервере. Я бы хотел "Привет!" появляться во время работы клиента, в вызове advance, иначе я не могу сделать многопользовательскую игру (всхлип)!


Однако, если я изменю код клиента на

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

the sever немедленно показывает "Привет!" . пока клиент ждет моего ввода.


Я попробовал, как было предложено в другом вопросе, использовать шаблоны ударов и hClose:

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

который делает вывод немедленно, без закрытия клиента. Это здорово. Но я планирую использовать bytestrings, потому что данные, которые я отправляю на сервер, сериализуется, поэтому я import qualified Data.ByteString as B и измените hGetContents на B.hGetContents, что заставляет проблему повторно появляться.


Проблема была действительно делом лени. hGetContents лениво читает все содержимое Handle, поэтому оно заканчивается только тогда, когда оно закрывается, когда клиент прерывает соединение. Вместо этого я использовал hGetLine, который возвращает содержимое каждый раз, когда он встречает \n, который я использую в качестве тега end-of-message.

Ответы

Ответ 1

Возможно, я ошибаюсь, но это не проблема hGetContents? Разумеется, это должно подождать, пока все содержимое, отправленное через ваш сокет, не будет отправлено до следующей строки (print...). hGetContents разработан, чтобы предоставить вам все содержимое, отправленное до закрытия сокета. Что-то вроде hGetLine может немедленно прекратиться, и вы можете оставить сокет открытым, чтобы отправить больше данных позже. Затем ваш клиент мог использовать hPutStrLn вместо hPutStr.

Ответ 2

По умолчанию используется вывод с линейной буферизацией, поэтому hPutStr (который не предоставляет окончание строки) не выводит ничего, пока вы не сбросите буфер. Есть два способа решить эту проблему:

a) Вызовите hFlush stdout вручную в любое время, когда вы хотите очистить вывод.

b) Используйте hSetBuffering, чтобы настроить буферизацию на NoBuffering

Все эти функции находятся в модуле System.IO.

Изменить: Ничего, я только видел, где вы сделали это в клиенте. Я отклоняю свой ответ с извинениями.

Ответ 3

Вероятно, вам нужно отключить алгоритм Nagle. Попробуйте этот код:

import Network.Socket

setSocketOption sock NoDelay 1