我有一台UDP服务器,可以反射(reflect)它收到的每个ping消息(我认为这很好用)。在客户端,我想做两件事:
似乎由于UDP的性质或
forkIO
的原因,我下面的客户端代码过早地结束了/根本没有做任何计数。同样令我惊讶的是,函数
tryOnePing
返回的是Int 4的250倍。为什么会这样呢?main = withSocketsDo $ do
s <- socket AF_INET Datagram defaultProtocol
hostAddr <- inet_addr host
thread <- forkIO $ receiveMessages s
-- is there any better way to eg to run that in parallel and make sure
-- that sending/receiving are asynchronous?
-- forM_ [0 .. 10000] $ \i -> do
-- sendTo s "ping" (SockAddrInet port hostAddr)
-- actually this would be preferred since I can discard the Int 4 that
-- it returns but forM or forM_ are out of scope here?
let tryOnePing i = sendTo s "ping" (SockAddrInet port hostAddr)
pings <- mapM tryOnePing [0 .. 1000]
let c = length $ filter (\x -> x==4) pings
-- killThread thread
-- took that out to make sure the function receiveMessages does not
-- end prematurely. still seems that it does
sClose s
print c
-- return()
receiveMessages :: Socket -> IO ()
receiveMessages socket = forever $ do
-- also tried here forM etc. instead of forever but no joy
let recOnePing i = recv socket 1024
msg <- mapM recOnePing [0 .. 1000]
let r = length $ filter (\x -> x=="PING") msg
print r
print "END"
最佳答案
这里的主要问题是,当您的主线程完成时,所有其他线程都会被自动杀死。您必须让主线程等待receiveMessages thread
,否则它很可能在收到任何响应之前就完成了。一种简单的方法是使用MVar
。MVar
是一个同步单元格,可以为空或仅保留一个值。如果当前线程尝试从空MVar
中获取或插入完整的()
,则它将阻塞。
在这种情况下,我们不在乎值本身,因此我们将在其中存储一个MVar
。
我们将从MVar
空开始。然后,主线程将派生出接收器线程,发送所有数据包,并尝试从()
中获取值。
import Control.Concurrent.MVar
main = withSocketsDo $ do
-- prepare socket, same as before
done <- newEmptyMVar
-- we need to pass the MVar to the receiver thread so that
-- it can use it to signal us when it's done
forkIO $ receiveMessages sock done
-- send pings, same as before
takeMVar done -- blocks until receiver thread is done
在接收器线程中,我们将接收所有消息,然后在
MVar
中放置一个sendTo
以表示我们已完成接收。receiveMessages socket done = do
-- receive messages, same as before
putMVar done () -- allows the main thread to be unblocked
这解决了主要问题,并且该程序可以在我的Ubuntu笔记本电脑上正常运行,但是还有其他一些要注意的事项。
"ping"
不保证将发送整个字符串。您必须检查返回值以查看发送了多少,如果没有全部发送就重试。如果发送缓冲区已满,即使是像recv
这样的短消息也可能发生这种情况。 recvFrom
需要连接的套接字。您将要改用MVar
。 (尽管由于某些未知原因,它仍然可以在我的PC上运行)。 ()
将用于传达接收到的数据包的数量,而不仅仅是MVar
。这样,您可以完成主线程的所有输出。或者,使用另一个ojit_code作为互斥体来控制对标准输出的访问。 最后,建议您仔细阅读Network.Socket,Control.Concurrent和Control.Concurrent.MVar的文档。我的大部分答案都是从那里找到的信息拼凑而成的。