【问题标题】:Using multi-threading / parallel to find a working port使用多线程/并行查找工作端口
【发布时间】:2018-10-09 08:59:26
【问题描述】:

我有一个类似下面的函数来连接一个端口不断变化的 FTP

private int FindWorkingPort(int from, int to)
{
    for (int port = from; port <= to; port++)
    {
        try
        {
            new FtpClient(_host, port).Connect();  // Instant on correct port, very slow on wrong port
            return port;
        }
        catch { }
    }

    throw new Exception("None of the port is working");
}

当第一次尝试正确时,它是即时的。否则,每次尝试可能需要 10 秒。你可以想象如果正确的是最后一个。

有没有办法让它同时尝试所有可能的端口? (我是多线程的菜鸟)

编辑:

我正在使用FluentFTP,它确实有.ConnectAsync()

【问题讨论】:

  • 1) 什么是FtpClient? 2)通常可以减少超时值,但这会产生错误的结果,如果确实存在超时问题 3)如果FtpClient 有异步API,您可以尝试并行连接请求,但这可能会受到您的网络基础设施的限制,以及主机网络基础设施。换句话说,不可能有 100% 有效且稳健的解决方案。你想达到什么目的?
  • 谢谢丹尼斯。 1)FluentFTP 2)你说得对,谢谢,所以如果它可以同时检查几个端口会更好 3)它确实支持异步。如果这就是您所说的,我不确定主机将如何处理多个连接。我想要实现的是更快地找到工作端口
  • @Aximili 您可以尝试 telnet 到端口而不是建立连接。
  • 请永远不要try { ... } catch { } - 这是一个糟糕的反模式。您应该只捕获您可以有意义地处理的特定异常。另外,当没有异常发生时不要抛出异常。
  • 这可能(或者应该)触发目标网络上的端口扫描警报。

标签: c# multithreading parallel-processing


【解决方案1】:

这不是最好但可行的解决方案:

var tasks = Enumerable.Range(from, to - from).ToList().Select(port => {
    try
    {
        return new FtpClient(_host, port).ConnectAsync().ContinueWith(_ => (int?)port);
    }
    catch
    {
        return Task.FromResult((int?)null);
    }
});
var foundPort = (await Task.WhenAll(tasks)).FirstOrDefault(p => p != null);

基本上,您创建一个您想要检查的所有端口的列表,并为每个端口启动一项任务。您等到所有这些都异步完成并获得第一个不是null 的。所有任务将并行运行,但await Task.WhenAll 将等待所有任务完成,这意味着它将始终等待约 10 秒(根据您的问题,检查不正确的端口通常需要约 10 秒)。

这可能会通过Task.WhenAny 得到改善,但是当其他任务抛出异常时返回正确的端口是很棘手的。

【讨论】:

  • 我明白你的意思了,感谢 FCin 的解释。因此,如果第一个是正确的(我希望大多数时候都是这样),使用 WhenAll 只会让情况变得更糟。而且我不知道该怎么做 .WhenAny(tasks where result != null)
  • @Aximili 正确,如果您必须检查 2-3 个端口,那么可能不值得,但如果您必须检查 >10,那么它变得有用,因为这里的等待时间是恒定的。我会更多地使用它,也许会找到一种方法来做Task.WhenAny。这样,您应该始终立即获得结果。
【解决方案2】:

这是另一种方法。这应该在第一个有效任务完成后立即返回:

public async Task<int> FindWorkingPortAsync(int from, int to)
{
    var wrapper = new TaskCompletionSource<int>();
    var tasks = Enumerable.Range(from, to - from).ToList().Select(port =>
    {
        return new FtpClient(_host, port).ConnectAsync().ContinueWith(t => {
            try
            {
                if(!wrapper.Task.IsCompleted && !t.IsFaulted && !t.IsCanceled)
                    wrapper.SetResult(port);
                return t;
            }
            catch
            {
                return Task.FromResult(-1);
            }
        });
    }).ToList();


    return await wrapper.Task;
}

让我知道它是否有效。我用HttpClient而不是FtpClient对其进行了测试。

【讨论】:

    【解决方案3】:

    您可以尝试使用多个线程同时连接到所有端口,但是随着更多线程的启动,性能会降低。我建议你试试 Task。

    这是一个任务和事件的例子。这不完全是解决方案,但你会得到这个要点。您可能需要更改代码结构。

        using System;
        using System.Threading.Tasks;
    
        public class Program
        {
            public class MyEventArgs : EventArgs
            {
                 public int port {get; set; }
    
                 public MyEventArgs(int port)
                 {
                     this.port = port;
                 }
            }
    
            delegate void OpenPort(object sender, MyEventArgs  e);
            event OpenPort OnOpen;
            int from = 10, to = 20;
            int Connected = 16;
    
            public static void Main()
            {
                var obj = new Program();
                // here you register your method ShowPort for OnOpen event. 
                obj.OnOpen += new OpenPort(ShowPort);
                for(int i = obj.from; i < obj.to; i++) {
                    obj.CheckPort(obj, i);
                }
            }
    
           private async void CheckPort(Program obj, int i) 
           {
               await CheckPortTask(obj, i);
           }
    
           private Task CheckPortTask(Program obj, int i)
           {
                 return Task.Run(() =>  {
                    if(i == obj.Connected)
                    {
                      obj.OnOpen(this, new MyEventArgs(i));
                    }
             });
           }            
    
            // This method will be called after connecting.
            public static void ShowPort(Object p, MyEventArgs args)
            {
                Console.WriteLine(args.port);
            }
        }
    

    如果您想尝试使用线程,请将上述代码中的 Task.Run 块替换为:

    new Thread(()=>{
                            if(i == obj.w) 
                            {
                                // this raises the event and calls the ShowPort method.
                                obj.OnOpen(obj, new MyEventArgs(i));
                            }
                        })).Start();
    

    【讨论】:

    • 任务也使用线程。 代码将在一个连接请求返回之前退出 - 你忘了等待任务
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-21
    相关资源
    最近更新 更多