Haskell 为什么这个TCP服务器会立即退出?
这是我从一个更大的项目中提取的内容,它似乎没有服务器立即返回的问题(我承认我提取内容的原因首先是希望问一个关于Haskell 为什么这个TCP服务器会立即退出?,haskell,tcp,conduit,stm,Haskell,Tcp,Conduit,Stm,这是我从一个更大的项目中提取的内容,它似乎没有服务器立即返回的问题(我承认我提取内容的原因首先是希望问一个关于接受失败的不同问题,因此代码可能还有其他问题) 我不认为运行更少的线程(好吧,一个线程)会是一个问题,但似乎悄悄地返回: starting tcp server exgetting protobuf port iting serveTBQ tcp server exited 预期的行为是,它将继续运行,在指定的端口上侦听(getPort) 以下是自包含的示例代码: #!/usr/bin
接受失败的不同问题,因此代码可能还有其他问题)
我不认为运行更少的线程(好吧,一个线程)会是一个问题,但似乎悄悄地返回:
starting tcp server
exgetting protobuf port
iting serveTBQ
tcp server exited
预期的行为是,它将继续运行,在指定的端口上侦听(getPort
)
以下是自包含的示例代码:
#!/usr/bin/env stack
{- stack script --nix --resolver lts-14.27
--nix-packages zlib
--no-nix-pure
--package bytestring
--package classy-prelude
--package conduit
--package exceptions
--package mtl
--package network
--package network-simple
--package stm
--package stm-conduit
--package text
--package unliftio
--ghc-options -Wall
-}
-- Use --verbose above for better error messages for library build failures
-- --package refined
-- --extra-dep unexceptionalio-0.5.1
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import ClassyPrelude hiding (hClose)
import Conduit
import Control.Concurrent.STM.TBQueue (TBQueue, writeTBQueue)
import Control.Monad.Catch (MonadMask)
import Control.Monad.Writer
import Data.Bits (shiftR, (.&.))
import qualified Data.ByteString.Char8 as B
import Data.Conduit.Async (gatherFrom)
import qualified Data.Conduit.List as CL
import Data.Function ((&))
import qualified Data.Text as T
import GHC.IO.Handle (Handle, hClose)
import qualified Network.Simple.TCP as TCP
import qualified Network.Socket as NS
import UnliftIO.Concurrent (ThreadId, forkIO, threadDelay)
type Error = [String]
type Result r = Writer Error r
runResult :: Result r -> (r, Error)
runResult = runWriter
getPort :: NS.ServiceName
getPort = "29876"
-- | This signature is meant to simulate the same function from the proto-lens library,
-- | but without dealing with protobus for binary data.
decodeMessageDelimitedH :: Handle -> IO (Either String String)
decodeMessageDelimitedH h = do
sOut <- B.hGetLine h
pure $ Right $ B.unpack sOut
protoServe :: forall m. (MonadMask m, MonadResource m, MonadUnliftIO m) =>
(String -> Result [String])
-> ConduitT () [String] m ()
protoServe fromProto = start .| mapMC logFilterRead
.| CL.catMaybes .| mapMC msgToRecs
where
port = trace "getting protobuf port" getPort
start = do
let enQserver = serveTBQ (TCP.HostIPv4) port (decodeProto . fst)
gatherFrom 10000 enQserver
decodeProto :: NS.Socket -> m (Either String String)
decodeProto sock = bracket
connHandleIO
(liftIO . hClose)
(liftIO . decodeMessageDelimitedH)
where
connHandleIO :: m Handle
connHandleIO = liftIO $ sockToHandle sock
logFilterRead :: Either String String -> m (Maybe String)
logFilterRead pEi = case pEi of
Right p -> pure $ Just p
Left err -> trace err $ pure Nothing
msgToRecs :: String -> m [String]
msgToRecs p = case runResult $ fromProto p of
(rs, rErr) -> do
when (not $ null rErr) $ pure $ trace (intercalate "\n" rErr) ()
pure $ trace "completed msgToRecs" rs
-- | The handle only needs a read-view of the socket. Note that a TBQeueue is
-- | mutable but has STM's runtime safety checks in place.
sockToHandle :: NS.Socket -> IO Handle
sockToHandle sock = NS.socketToHandle sock ReadMode
-- | Based on serve and listen from Network.Simple.TCP
-- | Unlike `serve`, which never returns, `serveTBQ` immediately returns
-- | a `TBQueue` of results.
serveTBQ :: forall a m. (MonadMask m, MonadUnliftIO m)
=> TCP.HostPreference -- ^ Host to bind.
-> NS.ServiceName -- ^ Server service port name or number to bind.
-> ((NS.Socket, NS.SockAddr) -> m a)
-- ^ Computation to run in a different thread once an incoming connection is
-- accepted. Takes the connection socket and remote end address.
-> TBQueue a -- ^ enqueue computation results to this queue
-> m ()
-- ^ Returns a FIFO (queue) of results from concurrent requests
serveTBQ hp port rFun tbq = do
_ <- async $ withRunInIO $ \run -> TCP.serve hp port $ \(lsock, _) -> do
run $ void $ acceptTBQ lsock rFun tbq
putStrLn $ T.pack "exiting serveTBQ"
-- | Based on acceptFork from Network.Simple.TCP.
acceptTBQ :: forall a m.
MonadUnliftIO m
=> NS.Socket -- ^ Listening and bound socket.
-> ((NS.Socket, NS.SockAddr) -> m a)
-- ^ Computation to run in a different thread once an incoming connection is
-- accepted. Takes the connection socket and remote end address.
-> TBQueue a
-> m ThreadId
acceptTBQ lsock rFun tbq = mask $ \restore -> do
(csock, addr) <- trace ("running restore-accept on lsock: " <> (show lsock)) $ restore (liftIO $ NS.accept lsock)
onException (forkIO $ finally
(restore $ do
rVal <- trace "retrieved rVal in finally-restore" rFun (csock, addr)
atomically $ writeTBQueue tbq rVal)
(TCP.closeSock csock))
(TCP.closeSock csock)
retryForever :: forall m a. MonadUnliftIO m => m a -> m a
retryForever prog = catchAny prog progRetry
where
progRetry :: SomeException -> m a
progRetry ex = do
putStrLn $ pack $ show ex
threadDelay 4000000
retryForever prog
-- | Safer interface to sinkNull
sinkUnits :: MonadResource m => ConduitT () Void m ()
sinkUnits = sinkNull
main :: IO ()
main = retryForever $ do
putStrLn $ T.pack "starting tcp server"
let myProtoServe = protoServe (pure . words)
myProtoServe .| mapMC (putStrLn . T.pack . intercalate "_") .| sinkUnits & runConduitRes
putStrLn $ T.pack "tcp server exited"
#/usr/bin/env堆栈
{-stack脚本--nix--resolver lts-14.27
--nix包zlib
--没有尼克斯纯
--通过测试环打包
--古典前奏曲
--包装导管
--包例外
--包装mtl
--包网络
--包网络简单
--封装扫描隧道显微镜
--封装stm导管
--包文本
--包装不合格
--ghc选项-墙
-}
--使用上面的--verbose可以获得更好的库生成失败错误消息
----包装精良
----额外dep非例外O-0.5.1
{-#语言无mplicitprelude}
{-#语言等级}
{-#语言范围类型变量#-}
模块主要在哪里
导入类关联隐藏(hClose)
导入导管
导入控制.Concurrent.STM.TBQueue(TBQueue,writeTBQueue)
导入控制.Monad.Catch(MonadMask)
导入控制.Monad.Writer
导入数据位(移位器、(.&.))
将限定数据.ByteString.Char8作为B导入
导入Data.conductor.Async(gatherFrom)
导入符合条件的数据.conductor.List作为CL
导入数据。函数(&))
导入符合条件的数据。文本为T
导入GHC.IO.Handle(Handle,hClose)
将合格的Network.Simple.TCP导入为TCP
将合格的网络套接字作为NS导入
导入UnliftIO.Concurrent(ThreadId、forkIO、threadDelay)
类型错误=[String]
类型结果r=写入程序错误r
runResult::Result r->(r,错误)
runResult=runWriter
getPort::NS.ServiceName
getPort=“29876”
--|此签名旨在模拟proto lens库中的相同功能,
--|但不处理二进制数据的协议。
decodeMessageDelimitedH::Handle->IO(任意字符串)
decodeMessageDelimitedH h=do
苏特
(字符串->结果[字符串])
->ConduitT()[String]m()
protoServe fromProto=开始。| mapMC logFilterRead
.| CL.catMaybes.| mapMC msgToRecs
哪里
端口=跟踪“获取协议端口”getPort
开始=做
让enQserver=serveTBQ(TCP.HostIPv4)端口(decodeProto.fst)
从10000个eQServer收集数据
解码协议::NS.Socket->m(任意一个字符串)
decode proto sock=括号
康汉德莱奥
(氯化锂)
(liftIO.decode.dh)
哪里
康汉德莱奥::m句柄
connHandleIO=liftIO$sockToHandle sock
logFilterRead::字符串->m(可能是字符串)
logFilterRead pEi=案例pEi
右p->纯$Just p
左错误->跟踪错误$pure Nothing
msgToRecs::String->m[字符串]
msgToRecs p=案例运行结果$fromProto p of
(重复,重复)->do
当(非$null rErr)$pure$trace(插入“\n”rErr)()
纯$trace“已完成的msgToRecs”rs
--|手柄只需要插座的读取视图。请注意,TBQUEUE是
--|可变,但有STM的运行时安全检查。
sockToHandle::NS.Socket->IO句柄
sockToHandle sock=NS.socketToHandle sock读取模式
--|基于Network.Simple.TCP的服务和监听
--|与永远不会返回的“serve”不同,“serveTBQ”会立即返回
--|结果的“TBQueue”。
serveTBQ::对于所有m。(单子任务m,单子任务m)
=>TCP.HostPreference--^要绑定的主机。
->NS.ServiceName--^要绑定的服务器服务端口名称或编号。
->((NS.Socket,NS.SockAddr)->m a)
--^一旦传入连接断开,计算将在其他线程中运行
--接受。获取连接套接字和远程端地址。
->TBQueue a--^将计算结果排入此队列
->m()
--^返回并发请求结果的FIFO(队列)
serveTBQ hp端口rFun tbq=do
_TCP.SERVICE hp端口$\(lsock,\)->do
运行$void$acceptTBQ lsock rFun tbq
putStrLn$T.pack“正在退出服务”
--|基于Network.Simple.TCP的acceptFork。
acceptTBQ::对于所有m。
单胞菌
=>NS.Socket--^侦听和绑定套接字。
->((NS.Socket,NS.SockAddr)->m a)
--^一旦传入连接断开,计算将在其他线程中运行
--接受。获取连接套接字和远程端地址。
->TBA队列
->m线程ID
acceptTBQ lsock rFun tbq=掩码$\restore->do
(csock,地址)m a
retryForever prog=捕获任何程序,然后重试
哪里
progRetry::SomeException->m a
PROGRETRYEX=do
putStrLn$pack$show ex
线程延迟4000000
retryForever程序
--|更安全的用户界面
sinkUnits::MonadResource m=>conduit()Void m()
sinkUnits=sinkNull
main::IO()
main=retryForever$do
putStrLn$T.pack“正在启动tcp服务器”
让myProtoServe=protoServe(纯文字)
myProtoServe.| mapMC(putStrLn.T.pack.夹层“|”).|下沉装置和运行导管
putStrLn$T.pack“tcp服务器已退出”
也许有一种方法可以让它在使用多个线程的同时仍然保留一个堆栈脚本?请参见在本例中,提取的服务器示例终止的原因是程序本身最终退出,从而终止所有其他线程(包括服务器正在运行的线程),而在我的实际应用程序中,主线程在p中已经有循环
waitForever :: IO ()
waitForever = do
threadDelay 10000
waitForever