Я хочу реализовать эффективную однопоточную связь сокетов с помощью управления событиями epoll
".
Если бы я написал очень императивную программу "с нуля", я бы сделал это в основном так (только какой-то псевдо-код, который я только что напечатал, вероятно, не будет компилироваться):
import Control.Concurrent
import Data.ByteString (ByteString)
import qualified Data.ByteString as ByteString
import qualified GHC.Event as Event
import Network
import Network.Socket
import Network.Socket.ByteString
main = withSocketFromSomewhere $ \ socket -> do
let fd = fromIntegral . fdSocket $ socket
-- Some app logic
state <- newMVar "Bla"
-- Event manager
manager <- Event.new
-- Do an initial write
initialWrite socket state manager
-- Manager does its thing
Event.loop manager
write manager socket bs =
-- Should be pretty straight-forward
Event.registerFd manager theWrite fd Event.evtWrite
where
fd = fromIntegral . fdSocket $ socket
theWrite key _ = do
Event.unregisterFd manager key
sendAll socket bs
read manager socket cont =
-- Ditto
Event.registerFd manager theRead fd Event.evtRead
where
fd = fromIntegral . fdSocket $ socket
theRead key _ = do
Event.unregisterFd manager key
bs <- recv socket 4096
cont bs
initialWrite socket state manager = do
msg <- readMVar state
write manager socket msg
read manager socket $ \ bs -> do
ByteString.putStrLn bs
putMVar state msg
Представьте, что есть также некоторые функции, которые добавляют события тайм-аута менеджеру и тому подобное.
Однако этот код не очень приятен, по нескольким причинам:
- Я провожу менеджер событий вручную.
- Я должен использовать
MVar
для моей логики приложения, потому что я не могу сказать непрозрачному менеджеру событий, что он должен передать мне какое-то состояние, хотя я знаю, что он использует только один поток и поэтому может потенциально использоваться как основание стека трансформатора монады. - Мне нужно создать явные разграниченные продолжения для чтения (И я мог бы даже сделать это для записи, я не знаю, что было бы мудрее в этой ситуации).
Теперь это просто кричит об использовании множества трансформаторов монады и т.д. Я бы хотел просто сделать это:
main =
withSocketFromSomewhere $ \ socket ->
runEvents . flip runStateT "Bla" $ initialWrite socket
initialWrite socket = do
msg <- lift get
write socket msg
resp <- read socket
liftIO $ ByteString.putStrLn resp
lift $ put msg
Этот код должен иметь такое же поведение, как и указанный выше код; например путем приостановки вычисления до тех пор, пока чтение не будет получено в строке resp <- read socket
и не позволит мне управлять несколькими сокетами в одном и том же потоке/менеджере.
Вопросы:
- Существует ли более высокоуровневый интерфейс для API/libevent/эквивалента событий GHC, который дает пользователю еще больше возможностей? Стоит ли даже рассматривать синхронные изменения планирования ввода-вывода, которые произошли в недавних GHC (я на 7.4.1)?
- Что делать, если я хочу реализовать совлокальный concurrency, например, имея одну функцию, которая всегда обрабатывает чтения из сокета, но имея эту функцию совместно использовать одну и ту же государственную монаду в качестве записи "поток"? Можно ли это сделать с любым решением из (1)?