2016-01-28 39 views
1

编辑:写在不同的线程的插座(在Haskell并发UDP服务器)

我写在Haskell的UDP BitTorrent的追踪。状态是基于我的数据类型ServerState传递到runUDPServeracceptConnections的handleConnectionhandleRequestData STM(二的TVar地图)。客户将要求开始“连接”,宣布或刮擦。每次有人向服务器发送消息时,他们都应该收到消息。 (协议在这里:http://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html)。

我会做一些二进制解析,IO monad中的一些处理(只是STM真的),并将二进制编码的消息发送回发件人。最初,我想我可以在自己的线程中像这样运行每个请求,但我想我可以分叉几个线程,让他们完成工作。其中一个问题可能是,整个服务器(所有线程)都会被n个发送UDP包的人阻止(但实际上这可能不是这样)。

我想我可以更清楚地定义我的问题:如果我只是fork了所有同时运行handleConnection的线程,那么是否会以某种方式与套接字混淆?另外,我怎么能理想地为每个接收到的数据包产生一个新的线程?

我的意思是,当我分叉几个线程并写入标准输出时,输出将混乱在从单独线程打印的内容之间。 Network.accept实际上提供了一个句柄,而个别线程并不需要知道套接字,但我不能使用接受我不会仅仅假设从多个线程同时写入套接字是安全的。

{-# LANGUAGE OverloadedStrings #-} 

import Control.Exception (bracket) 
import qualified Data.ByteString.Char8 as BS 
import qualified Network.Socket as S hiding (send, sendTo, recv, recvFrom) 
import qualified Network.Socket.ByteString as S 

runUDPServer serverState port = 
    S.withSocketsDo $ bracket (createSocket port) S.close (acceptConnections serverState) 

    where 
     createSocket port = do 
      serverAddr <- fmap head $ S.getAddrInfo 
       (Just (S.defaultHints {S.addrFlags = [S.AI_PASSIVE]})) 
       Nothing 
       (Just port) 

      socket <- S.socket (S.addrFamily serverAddr) S.Datagram S.defaultProtocol 
      S.bind socket $ S.addrAddress serverAddr 

      return socket 

     acceptConnections serverState socket = do 
      handleConnection serverState socket 
      acceptConnections socket 

     handleConnection serverState socket = do 
      (requestData, remoteAddress) <- S.recvFrom socket 2048 
      responseData <- handleRequestData serverState requestData remoteAddress 
      S.sendTo socket responseData remoteAddress 

     handleRequestData :: ServerState -> BS.ByteString -> S.SockAddr -> IO BS.ByteString 
     handleRequestData serverState requestData remoteAddress = do 
      putStrLn "-----" 

      putStrLn $ "Received UDP message" 
      putStrLn $ "Address: " ++ show remoteAddress 

      -- (left out code here) 
      return "Dummy ByteString" 

我将是任何提示非常感谢,指针等

+1

“我宁愿没有一个线程池,每个都运行recvFrom,但我想我可以。我不知道如何以合理的方式从多线程写入套接字。”为什么不?这一切都应该是在POSIX原子,对不对?如果是这样,你可以'replicateM n(forkIO $ forever $ handleConnection socket)'。我不知道是否会对UDP套接字上的争用造成性能影响。如果是这样,那么让一个线程读取数据并将其转换为阻塞的FIFO队列可能会更快。 – jberryman

+0

@jberryman您可以改为动态创建线程,而不是一组线程,每个连接一个线程。 – PyRulez

+0

此问题未包含足够的信息以获得完整答案。例如,你想收集所有客户提供的信息吗?你需要通过客户收集它们吗?你是否确认你目前的方法不够快/“并发”? UDP是否适合您的用例? – Zeta

回答

-1

你需要学习一些并发的兄弟!

你需要学习的第一件事是forkIO :: IO() -> IO ThreadId。这是所有并发开始的地方。你给它一个IO动作,并启动一个线程来运行这个IO动作,就像魔术师一样!一切,包括你所说的东西accept,可以追溯到forkIO!由于Haskell数据是不可变的,所以它是完全安全的(如果你不使用锁定,没有问题(然后死锁是不可能的)。)

Haskell的其余学习并发性是如何使用forkIO和库基于forkIO(和一些相关的基元)。首先,阅读Control.Concurrent。然后阅读Parallel and Concurrent Haskell(免费电子书)。要了解该书如何帮助您的情况,请转至Chapter 12

Haskell是在并发处理比任何必要的和/或不纯的语言,除非并发(非Haskellers提示downvotes)专门打造更好。拥抱它。


好的,为此,您将使用某种渠道。 MVar实际上就足够了(只要你写的比你制作的快)。请参阅this。如果生产速度超过您的撰写速度,请参阅this

stm包具有相似的结构。

+1

正如你在谈论'IO'没有一个“Haskell是不可变的,死锁是不可能的,也好多了”有任何意义 - 你可以在这里使用* f +++ *和其他任何语言一样可怕 – Carsten

+0

@Carsten我说*如果你不使用锁*,那么死锁是不可能的。当然,你可以在Haskell中犯下可怕的错误,但是只有当你使用明确使用的可变变量时,你才会变得更好,因为你可以控制什么是可变的。 – PyRulez

+0

感谢您的链接!我只是不确定从多线程同时写入套接字的语义。此外,您链接到的第12章中的示例使用_Network.accept_。 –

0

我个人倒有一个线程进行读取,只是称为一个循环recv,折腾到一个Chan请求,然后另一个用于写,后台掀起了Chan。但是,我确实怀疑,尽管无法证实,您可能有多个线程直接执行此操作,就像在您描述的架构中那样。我认为这可能是好的原因是IO通过处理多路复用的IO管理器传输。虽然没有包含最新的发展,但我认为MIO paper应该涵盖目前如何实施的基本知识。