从不同的线程(Haskell中的并发UDP服务器)写入套接字

时间:2016-01-28 15:57:22

标签: multithreading sockets haskell server udp

修改

我正在Haskell中编写UDP BitTorrent跟踪器。状态基于STM(两个TVar Maps)以我的数据类型ServerState传递到 runUDPServer acceptConnections handleConnection handleRequestData 。客户要么开始“连接”,要么宣布或要求。每当有人向服务器发送消息时,他们都应该收到消息。 (协议在这里:http://www.rasterbar.com/products/libtorrent/udp_tracker_protocol.html)。

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

我想我可以更明确地定义我的问题:如果我只是分叉所有同时运行handleConnection的线程,那么它会以某种方式搞乱套接字吗?另外,(如何)我可以理想地以某种方式为每个接收的数据包生成一个新线程?

我的意思是,当我分叉几个线程并写入stdout时,输出将是乱码/从单独线程打印的内容之间的混合。 Network.accept 实际上提供了一个句柄,各个线程并不真正需要了解套接字,但我不能使用 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"

我会非常感谢任何提示,指示等。

2 个答案:

答案 0 :(得分:0)

就个人而言,我只有一个线程用于读取,只是在一个循环中调用recv,然后将请求转移到Chan然后另一个用于写入,假脱机{{1} }。但是,我确实怀疑,虽然无法确认,您可以在您描述的架构中直接执行多个线程。我认为这可能没问题的原因是IO通过IO管理器进行流量处理,IO管理器处理多路复用。虽然不包含最新的发展,但我认为MIO paper应涵盖目前实施方式的基础知识。

答案 1 :(得分:-1)

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

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

Haskell中学习并发的其余部分是如何使用forkIO和基于forkIO的库(以及一些相关的原语)。首先,阅读Control.Concurrent。然后阅读Parallel and Concurrent Haskell(免费电子书)。要了解本书如何帮助您处理您的情况,请转到Chapter 12

Haskell 更好地处理并发性比任何命令式和/或不纯的语言更好除非专门为并发而构建(cue downvotes from non-Haskellers)。拥抱它。

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

stm包具有类似的结构。