【问题标题】:How to write a minimal-overhead proxy to localhost:3389 in Haskell?如何在 Haskell 中为 localhost:3389 编写一个最小开销的代理?
【发布时间】:2012-02-27 01:36:27
【问题描述】:

更新:问题现在包含最终编辑的答案!

我现在使用以下(最终答案):

module Main where

import Control.Concurrent        (forkIO)
import Control.Monad             (when,forever,void)
import Network                   (PortID(PortNumber),listenOn)
import Network.Socket hiding     (listen,recv,send)
import Network.Socket.ByteString (recv,sendAll)
import qualified Data.ByteString as B
import System

type Host = String
type Port = PortNumber

main :: IO ()
main = do
  [lp,h,p] <- getArgs  
  start (port lp) h (port p)
  where
    port = fromInteger . read

start :: Port -> Host -> Port -> IO ()
start lp rh rp = withSocketsDo $ do
  proxy <- listenOn $ PortNumber lp
  forever $ do
    (client,_) <- accept proxy
    void . forkIO $ (client >-<) =<< rh .@. rp

(.@.) :: Host -> Port -> IO Socket
host .@. port = do
  addr:_ <- getAddrInfo Nothing (Just host) (Just $ show port)
  server <- socket (addrFamily  addr) Stream defaultProtocol
  connect server   (addrAddress addr)
  return  server

(>-<) :: Socket -> Socket -> IO ()
x >-< y = do x >- y; y >- x

(>-) :: Socket -> Socket -> IO ()
s >- r = void . forkIO . handle $ forever stream
  where
    stream = recv s (64 * 1024) >>= ifNot0 >>= sendAll r
    ifNot0  = \c -> do when (B.null c) $ handle (error "0"); return c
    handle = flip catch $ \e -> print e >> sClose s >> sClose r

可以这样运行:

proxy 2000 localhost 3389

使用mRemote,如果我连接到localhost:2000,我看到本地机器的登录屏幕! :)

*如果我找到进一步改进(&gt;-) 的方法,我会更新这个答案!

【问题讨论】:

  • 你想用他的代码解决什么具体问题?
  • @MikePennington ,我只是在问题的最后描述了我要解决的问题。
  • @Cetin Sert:所以你的问题是如何让它更快?
  • @NiklasB。更快并且使用尽可能少的内存,无需在 (&gt;-) 内创建 content
  • 我没有关于如何加快速度的好建议,但对风格有两点说明。 ignore 函数已在 Control.Monad 中可用,名称为 void// 的名称也不太具描述性,我最好使用普通的 sClose from &gt;&gt; sClose to

标签: sockets networking haskell proxy network-programming


【解决方案1】:

你好像是在找资料的时候来this tcp proxy gist的。这时候,是破碎的,有点凌乱。在这种情况下,请不要犹豫,联系作者(在这种情况下是我),以便他可以修复要点以供将来参考:)

我会尽快修复它并链接到这个 SO 问题。固定版本将包括 sendAll 以及来自此 SO 问题的所有不错的建议,所以请分享您的最佳想法。附带说明一下,this branch of throttle 已经修复了 sendAll,以备不时之需。

编辑:现在已经修复了要点

【讨论】:

  • 我仍然要赶上 github 的快节奏环境,所以像 gists 和 pings 这样的概念有点超出我目前的视野 :) 。我很高兴现在要修复要点。
  • 最简单的方法可能是在 Gist 本身中添加评论,但不幸的是,您必须登录才能这样做。像许多人一样,我倾向于使用 Gist 来粘贴代码,以便与伙伴分享快速而肮脏的概念。结果,我的要点(和许多其他人的要点)几乎总是设计得很糟糕而且被打破了。这就是为什么如果搜索引擎碰巧索引了其中一个,并且有些人觉得它很有趣,那么让作者知道他的丑陋黑客需要一点小心是非常重要的:)
  • 48 行代码,我们有一个可靠、低内存、高性能的可爱代理。并且代码非常清晰,它是真正的可执行意识流,它没有噪音,但在我遇到过的最可爱的函数式编程语言中带有纯粹的意图。我真的很高兴也很感激我偶然发现了你的要点。 :)
  • 比以前更短更切题了:) &lt;~~&gt;!我很高兴能够在 Haskell 中更广泛地接受花哨的符号。
【解决方案2】:

几个月前我开始使用 Haskell 时发现 this gist

真的简单易懂。

编辑:根据上面的要点,这是一个经过测试的 RDP 代理。不同之处在于将 send 替换为 sendAll 以确保所有数据都已交付。通过linux rdp服务器测试时发现这个问题(大payload断开)。

module Main where

import Control.Concurrent      (forkIO)
import Control.Monad           (forever,unless)
import Network                 (PortID(PortNumber),listenOn)
import qualified Data.ByteString as S
import Network.Socket hiding (listen,recv,send)
import Network.Socket.ByteString (recv,sendAll)
import System.Posix            (Handler(Ignore),installHandler,sigPIPE)


localPort :: PortNumber
localPort = 3390

remoteHost :: String
remoteHost = "localhost"

remotePort :: Integer
remotePort = 3389

main :: IO ()
main = do
  ignore $ installHandler sigPIPE Ignore Nothing
  start

start :: IO ()
start = withSocketsDo $ do
  listener <- listenOn $ PortNumber localPort
  forever $ do
    (client,_) <- accept listener
    ignore $ forkIO $ do
      server <- connectToServer
      client `proxyTo` server
      server `proxyTo` client
    return ()
  where
    connectToServer = do
      addrinfos <- getAddrInfo Nothing (Just remoteHost) (Just $ show remotePort)
      let serveraddr = head addrinfos
      server <- socket (addrFamily serveraddr) Stream defaultProtocol
      connect server (addrAddress serveraddr)
      return server
    proxyTo from to = do
      ignore $ forkIO $ flip catch (close from to) $ forever $ do
        content <- recv from 1024
        unless (S.null content) $ sendAll to content
      return ()
    close a b _ = do
      sClose a
      sClose b

-- | Run an action and ignore the result.
ignore :: Monad m => m a -> m ()
ignore m = m >> return ()

【讨论】:

  • 感谢您的链接。我实际上在问题中使用了相同的...,我的问题是关于 proxyTo 的效率,但我想我很快不会得到答案,或者可能很难改进它。
  • 修复了来自 github 的代码。它和你的代码有同样的问题,破坏了数据流。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-21
  • 2011-06-24
  • 2011-03-25
  • 2023-03-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多