简体   繁体   English

haskell TCP 服务器向所有连接发送传入消息

[英]haskell TCP Server which sends an incoming message to all connections

What is the best way to write a tcp server in haskell which mantains a pool of all open connections and sends all the messages it receives to all connections?在 haskell 中编写 tcp 服务器的最佳方法是什么,该服务器维护所有打开的连接池并将其接收到的所有消息发送到所有连接?

basically a translation of this nodejs code基本上是这个nodejs代码的翻译

import net from "net";

const conns = [];

net.createServer((socket) => {
    conns.push(socket);

    socket.on("data", (data) => {
        for (const conn of conns) {
            conn.write(data);
        }
    });

    socket.on("close", () => {
        conns.splice(conns.indexOf(socket), 1);
    });
}).listen(1337);

It's always hard to say what the "best" way is.总是很难说什么是“最好的”方式。 Here's one way:这是一种方法:

import Control.Concurrent
import Control.Concurrent.Async  -- from async
import Control.Monad
import Control.Monad.Loops       -- from monad-loops
import Network.Simple.TCP        -- from network-simple

chunk = 4096

main = do
  bcast <- newChan
  forkIO $ forever $ readChan bcast
  serve (Host "127.0.0.1") "8080" $ \(s, addr) -> do
    bcast' <- dupChan bcast
    race_ (forever $ readChan bcast' >>= send s)
          (whileJust_ (recv s 4096) $ writeChan bcast')

In more detail, Control.Concurrent has channels that support simple broadcast operation via a dupChan operation.更详细地说, Control.Concurrent具有通过dupChan操作支持简单广播操作的通道。 Here, we create a master broadcast channel:在这里,我们创建一个主广播频道:

bcast <- newChan

This master channel and any of its duplicates created via dupChan can be written to, and the resulting data will be available on all duplicated copies.可以写入此主通道及其通过dupChan创建的任何副本,并且生成的数据将在所有副本上可用。

Because we will use the master channel only for making duplicates and not for reading and writing, we'll need to fork a thread to drain it so data doesn't accumulate:因为我们将只使用主通道进行复制而不是读取和写入,所以我们需要分叉一个线程来排空它,这样数据就不会累积:

forkIO $ forever $ readChan bcast

Now, we use serve to serve clients.现在,我们使用serve来服务客户。 For each client that connects, the do-block is run:对于每个连接的客户端,都会运行 do-block:

serve (Host "127.0.0.1") "8080" $ \(s, addr) -> do ...

In the do-block, we create our per-client duplicate of the master channel:在 do-block 中,我们创建主通道的每个客户端副本:

bcast' <- dupChan bcast

Then, we fork two threads, one that reads any data broadcast on the bcast' channel and sends it out to the socket, and another that reads input from the socket and broadcasts it.然后,我们分叉两个线程,一个读取bcast'频道上的任何数据广播并将其发送到套接字,另一个从套接字读取输入并广播它。 We race_ these threads so that if either one completes, the other will be killed:我们race_这些线程进行比赛,以便如果其中一个完成,另一个将被杀死:

race_ (forever $ readChan bcast' >>= send s)
      (whileJust_ (recv s 4096) $ writeChan bcast')

Usually, if a client disconnects, recv will return Nothing and the second thread will end.通常,如果客户端断开连接, recv将返回Nothing ,第二个线程将结束。 This will cause race_ to kill the first.这将导致race_杀死第一个。

There is a small race condition however.然而,有一个小的竞争条件。 If a client disconnects and a broadcast is sent and processed by the first thread before the second thread returns and race_ kills the first, send will raise an exception.如果客户端断开连接并且在第二个线程返回之前第一个线程发送并处理了广播并且race_杀死了第一个,则send将引发异常。 This will cause race_ to kill the second thread (as desired) but then re-raise the exception, resulting in an ugly error message on the console.这将导致race_杀死第二个线程(根据需要),然后重新引发异常,从而导致控制台上出现丑陋的错误消息。

You can replace race_ with raceQuietly_ defined as follows if you want to avoid this:如果您想避免这种情况,您可以将race_替换为如下定义的raceQuietly_

import Control.Exception.Safe

raceQuietly_ x y = race_ x y `catchIO` (\_ -> return ())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM