我有一台UDP服务器,可以反射(reflect)它收到的每个ping消息(我认为这很好用)。在客户端,我想做两件事:

  • 确保我触发了N(例如10000条)消息,并且
  • 计算正确接收到的响应的数量。

  • 似乎由于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.SocketControl.ConcurrentControl.Concurrent.MVar的文档。我的大部分答案都是从那里找到的信息拼凑而成的。

    10-07 14:54