2016-01-28 57 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應該涵蓋目前如何實施的基本知識。