英文:
Haskell STM not giving output or deriving Show not giving any output
问题
我正在用Haskell编写一个Telnet控制台,使用以下代码:
module Main (main) where
import qualified Data.ByteString.Char8 as C
import Control.Concurrent (forkFinally)
import qualified Control.Exception as E
import Control.Monad (unless, forever, void)
import Control.Monad.STM (STM, atomically)
import Control.Concurrent.STM.TVar (TVar, newTVar, writeTVar, readTVar)
import qualified Data.ByteString as S
import Network.Socket (close, setSocketOption)
import Network.Socket (HostName)
import Network.Socket (ServiceName, AddrInfo)
import Network.Socket (Socket, SocketType(Stream))
import Network.Socket (addrFlags, getAddrInfo, gracefulClose, accept, listen, addrAddress, bind, setCloseOnExecIfNeeded, openSocket, withFdSocket, SocketOption(ReuseAddr))
import Network.Socket (addrSocketType, defaultHints, AddrInfoFlag(AI_PASSIVE))
import Network.Socket.ByteString (recv, sendAll)
data Störm = An | Aus deriving Show
someFunc :: IO ()
someFunc = runTCPServer Nothing "3000" (talk $ newTVar Aus)
where
talk :: STM (TVar Störm) -> Socket -> IO ()
talk state' s = recv s 1024 >>= \msg -> unless (S.null msg) $
case C.unpack msg of
"exit\r\n" -> sendAll s (C.pack "Auf Widerhoren")
"an\r\n" -> sendAll s (C.pack "Am An\n") >> putStrLn "Am An" >> write state' An >> talk state' s
"aus\r\n" -> sendAll s (C.pack "Am Aus\n") >> putStrLn "Am Aus" >> write state' Aus >> talk state' s
"anzeigen\r\n" -> sendAll s <$> (atomically $ C.pack <$> show <$> (state' >>= readTVar)) >> putStrLn "Am Anzeigen State" >> talk state' s
otherwise -> sendAll s (C.pack "Unbekannte Befehl\n") >> putStrLn (show msg) >> (talk (state') s)
write :: STM (TVar Störm) -> Störm -> IO ()
write state new = atomically $ state >>= \s -> writeTVar s new
runTCPServer :: Maybe HostName -> ServiceName -> (Socket -> IO a) -> IO a
runTCPServer mhost port server = resolve >>= \addr -> (E.bracket (open addr) close loop)
where
resolve :: IO AddrInfo
resolve = do
let hints = defaultHints {
addrFlags = [AI_PASSIVE]
, addrSocketType = Stream
}
head <$> getAddrInfo (Just hints) mhost (Just port)
open :: AddrInfo -> IO Socket
open addr = E.bracketOnError (openSocket addr) close $ \sock -> do
setSocketOption sock ReuseAddr 1
withFdSocket sock setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
return sock
loop :: Socket -> IO b0
loop sock = forever $ E.bracketOnError (accept sock) (close . fst)
$ \(conn, _peer) -> void $
-- 'forkFinally' alone is unlikely to fail thus leaking @conn@,
-- but 'E.bracketOnError' above will be necessary if some
-- non-atomic setups (e.g. spawning a subprocess to handle
-- @conn@) before proper cleanup of @conn@ is your case
forkFinally (server conn) (const $ gracefulClose conn 5000)
它使用以下包:
base ^>=4.16.4.0
, network ^>=3.1.4.0
, bytestring
, stm
当从Telnet会话连接到控制台并运行an
或aus
,然后运行anzeigen
,或者直接连接到控制台时,控制台不会返回相应的An
或Aus
消息给Telnet客户端,而只会返回一个空格。同时,我知道选择是正确的,因为服务器的stdout中显示了消息“Am Anzeigen State”。
我不知道为什么会发生这种情况。
英文:
I am writing a telnet console in haskell with this code
module Main (main) where
import qualified Data.ByteString.Char8 as C
import Control.Concurrent (forkFinally)
import qualified Control.Exception as E
import Control.Monad (unless, forever, void)
import Control.Monad.STM (STM, atomically)
import Control.Concurrent.STM.TVar (TVar, newTVar, writeTVar, readTVar)
import qualified Data.ByteString as S
import Network.Socket (close, setSocketOption)
import Network.Socket (HostName)
import Network.Socket (ServiceName, AddrInfo)
import Network.Socket (Socket, SocketType(Stream))
import Network.Socket (addrFlags, getAddrInfo, gracefulClose, accept, listen, addrAddress, bind, setCloseOnExecIfNeeded, openSocket, withFdSocket, SocketOption(ReuseAddr))
import Network.Socket (addrSocketType, defaultHints, AddrInfoFlag(AI_PASSIVE))
import Network.Socket.ByteString (recv, sendAll)
data Störm = An | Aus deriving Show
someFunc :: IO ()
someFunc = runTCPServer Nothing "3000" (talk $ newTVar Aus)
where
talk :: STM (TVar Störm) -> Socket -> IO ()
talk state' s = recv s 1024 >>= \msg -> unless (S.null msg) $
case C.unpack msg of
"exit\r\n" -> sendAll s (C.pack "Auf Widerhoren")
"an\r\n" -> sendAll s (C.pack "Am An\n") >> putStrLn "Am An" >> write state' An >> talk state' s
"aus\r\n" -> sendAll s (C.pack "Am Aus\n") >> putStrLn "Am Aus" >> write state' Aus >> talk state' s
"anzeigen\r\n" -> sendAll s <$> (atomically $ C.pack <$> show <$> (state' >>= readTVar)) >> putStrLn "Am Anzeigen State" >> talk state' s
otherwise -> sendAll s (C.pack "Unbekannte Befehl\n") >> putStrLn (show msg) >> (talk (state') s)
write :: STM (TVar Störm) -> Störm -> IO ()
write state new = atomically $ state >>= \s -> writeTVar s new
runTCPServer :: Maybe HostName -> ServiceName -> (Socket -> IO a) -> IO a
runTCPServer mhost port server = resolve >>= \addr -> (E.bracket (open addr) close loop)
where
resolve :: IO AddrInfo
resolve = do
let hints = defaultHints {
addrFlags = [AI_PASSIVE]
, addrSocketType = Stream
}
head <$> getAddrInfo (Just hints) mhost (Just port)
open :: AddrInfo -> IO Socket
open addr = E.bracketOnError (openSocket addr) close $ \sock -> do
setSocketOption sock ReuseAddr 1
withFdSocket sock setCloseOnExecIfNeeded
bind sock $ addrAddress addr
listen sock 1024
return sock
loop :: Socket -> IO b0
loop sock = forever $ E.bracketOnError (accept sock) (close . fst)
$ \(conn, _peer) -> void $
-- 'forkFinally' alone is unlikely to fail thus leaking @conn@,
-- but 'E.bracketOnError' above will be necessary if some
-- non-atomic setups (e.g. spawning a subprocess to handle
-- @conn@) before proper cleanup of @conn@ is your case
forkFinally (server conn) (const $ gracefulClose conn 5000)
which uses the packages
base ^>=4.16.4.0
, network ^>=3.1.4.0
, bytestring
, stm
When connect to the telnet to the console from a telnet session, and run an
or aus
and then anzeigen
or just directly the console not returns the corresponding An
or Aus
message to the telnet client but only an space, meanwhile I know that the case is being properly selected because the message Am Anzeigen State
is being showed in the server's stdout.
I have not idea why this is happening.
答案1
得分: 2
问题在于你没有发送任何内容。
在你的 "anzeigen" 处理程序中,你有这段代码(将 partThatDoesntMatter = (atomically $ C.pack <$> show <$> (state' >>= readTVar))
重命名为:
sendAll s <$> partThatDoesntMatter >> putStrLn "Am Anzeigen State"
这只是上面代码的一个替代写法:
fmap (sendAll s) partThatDoesntMatter >> putStrLn "Am Anzeigen State"
你想要在这里使用 sendAll
发送一些内容,但是你这里的 fmap
调用只是产生一个 sendAll
操作,它永远不会被执行(并且立即被之后的 >>
丢弃)。
你需要的是:
partThatDoesntMatter >>= sendAll s >> putStrLn "Am Anzeigen State"
比较一下:
fmap :: (a -> b) -> m a -> m b
(>>=) :: m a -> (a -> m b) -> m b
应用到我们的例子上,这就是:
sendAll s :: ByteString -> IO ()
partThatDoesntMatter :: IO ByteString
fmap (sendAll s) partThatDoesntMatter :: IO (IO ())
partThatDoesntMatter >>= sendAll s :: IO ()
英文:
The problem is that you're not sending anything.
In your "anzeigen" handler, you have this code (renaming partThatDoesntMatter = (atomically $ C.pack <$> show <$> (state' >>= readTVar))
sendAll s <$> partThatDoesntMatter >> putStrLn "Am Anzeigen State"
This just an alternate spelling of
fmap (sendAll s) partThatDoesntMatter >> putStrLn "Am Anzeigen State"
You want to send something using sendAll
here, but your fmap
call here is just producing a sendAll
action that never gets executed (and immediately discarded by the >>
afterwards).
What you need here is
partThatDoesntMatter >>= sendAll s >> putStrLn "Am Anzeigen State"
Compare:
fmap :: (a -> b) -> m a -> m b
(>>=) :: m a -> (a -> m b) -> m b
applied to our example this is the difference between
sendAll s :: ByteString -> IO ()
partThatDoesntMatter :: IO ByteString
fmap (sendAll s) partThatDoesntMatter :: IO (IO ())
partThatDoesntMatter >>= sendAll s :: IO ()
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论