【问题标题】:Using Await to run parallel HTTP requests使用 Await 运行并行 HTTP 请求
【发布时间】:2020-05-11 20:27:07
【问题描述】:

所以这是来自> [Using Parallel Processing in C# to test a site's ability to withstand a DDOS

的继续

我使用这篇 MS KB 文章作为我的示例的基础,除了我不想通过单击按钮运行它,而是通过控制台脚本启动它,然后触发“攻击”方法,该方法循环通过 X 没有相同的 URL,并在返回时返回。这个example from MS 只是向 Microsoft URLS 发出 3 个请求。

为了使它更像知识库文章,我刚刚将按钮单击替换为在控制台应用程序运行时调用的主程序,该程序又调用 Attack(),这是唯一的(目前只是试图获得3 个网址)。在真正的代码中,我有一个循环,但我需要先让这部分工作,这样我才能理解我做错了什么。

但是,当我在命令提示符下运行它时,我得到的只是......

C:\Users\XXX>"C:\Users\XXX\Documents\Visual Studio 2017\Projects\DOSBot\DOSBot
\bin\Release\DOSBot.exe" "https://www.google.com" 1000
10/05/2020 00:00:00: starting script

然后它就结束了,没有像我期望的那样从 HTTP 请求返回消息。

控制台脚本代码如下:

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;


namespace AsyncExample_MultipleTasks
{
    class Program
    {
        // Replaced the MS KB article example of hitting a button with just a Main constructor that runs from the console. I am passing arguments in at the moment URL and no of Requests to make but not currently using them as I want to understand why the MS KB example of making 3 requests is not working
        public static void Main(string[] args)
        {
            if (args.Length > 0)
            {

                string url = args[0];
                int no = Convert.ToInt32(args[1]);

                ShowDebug("starting script");

                // Attack DOS = new Attack(url,no);
                Attack DOS = new Attack();

                // Moved from the Attack() constructor but made no difference
                DOS.StartAttack();

                // The job just seems to end with no error message and DOS.StartAttack() seems to be skipped over
            }
        }

        public static void ShowDebug(string msg)
        {
            string debugmsg = DateTime.Now.Date.ToString() + ": " + msg;

            Console.WriteLine(debugmsg);
        }
    }


    public class Attack
    {
        private int Counter = 0; // will hold no of actual HTTP requests completed
        private string URL; // URL to hit
        private int ReqNo; // No of HTTP request to make

        public Attack()//string url, int reqNo=100)
        {
            this.URL = "http://www.google.com";// url;
            this.ReqNo = 100; //reqNo;

            //Tried calling this from here but now from the Main Program Constructor but makes no difference
            //this.StartAttack();
        }

        // Tried calling this from the constructor above (commented out), and now from the main Program but neither do anything different
        public async void StartAttack()
        {
            await CreateMultipleTasksAsync();
        }

        // This would be replaced by my loop with one URL to request and X no of times to request it.
        private async Task CreateMultipleTasksAsync()
        {
            // Declare an HttpClient object, and increase the buffer size. The  
            // default buffer size is 65,536.  
            HttpClient client =
                new HttpClient() { MaxResponseContentBufferSize = 1000000 };

            // Create and start the tasks. As each task finishes, DisplayResults
            // displays its length.  
            Task<int> download1 =
                ProcessURLAsync("https://msdn.microsoft.com", client);
            Task<int> download2 =
                ProcessURLAsync("https://msdn.microsoft.com/library/hh156528(VS.110).aspx", client);
            Task<int> download3 =
                ProcessURLAsync("https://msdn.microsoft.com/library/67w7t67f.aspx", client);

            // Await each task.  
            int length1 = await download1;
            int length2 = await download2;
            int length3 = await download3;

            int total = length1 + length2 + length3;

            // Display the total count for the downloaded websites.  
            Program.ShowDebug("\r\n\r\nTotal bytes returned:  {total}\r\n");
        }

        async Task<int> ProcessURLAsync(string url, HttpClient client)
        {
            var byteArray = await client.GetByteArrayAsync(url);
            DisplayResults(url, byteArray);
            return byteArray.Length;
        }

        // Why is this not firing on return of the HTTP request?
        private void DisplayResults(string url, byte[] content)
        {
            // Display the length of each website. The string format
            // is designed to be used with a monospaced font, such as  
            // Lucida Console or Global Monospace.  
            var bytes = content.Length;
            // Strip off the "https://".  
            var displayURL = url.Replace("https://", "");
            Program.ShowDebug($"\n{displayURL,-58} {bytes,8}");
        }
    }
}

所以 DisplayResults 方法根本没有被调用,理论上从我读过的内容来看,当 HTTP 响应返回字节数等时应该触发它。但是我根本看不到它正在运行。

我是否缺少参考资料或其他内容?我在 Visual Studio 2017 的 64 位 Windows 笔记本电脑上使用 .NET 4.6.1。

【问题讨论】:

  • 提示:如果您不等待StartAttack,那么您的程序执行将移至Main 方法的下一行,然后是下一行,然后您的程序结束。另请注意,从 C# 版本开始......呃,7.1?异步主要方法是可能的。
  • 这里的问题是你的程序在调用DOS.StartAttack之后就存在了。因为, main 不会等待方法执行完成,这就是为什么您看不到 DisplayResults 被调用的原因。将public async void StartAttack() 更改为public async Task StartAttack() 并以DOS.StartAttack().GetResult().GetAwaiter(); 调用。或者,您可以使用 async main 方法。
  • 请注意是否应该在 SO 上发布此类内容?
  • 当我在构造函数中更改 public Attack(), this.StartAttack() 到 this.StartAttack().GetResult().GetAwaiter(); - 我收到 GetResult() 不是方法(不在我的课程中)的错误。 - 如果我在程序中更改最初运行的 Main 方法并创建类(希望)在并行 HTTP 请求中循环到 public async Main(string[] args) - 我得到“找不到类型或命名空间异步“ - 另外,如果我将 Main 更改为 public async void Main(string[] args) 我得到“程序不包含入口点的静态 Main 方法” - 请提供更多示例代码

标签: c# http parallel-processing async-await ddos


【解决方案1】:

了解异步编程的一些重要原则:

  1. 异步 ​​!= 并行。您的代码中没有任何“并行”发生。

    • “并行”表示同时执行两行代码。这只能通过多个线程来完成。一切都是关于事情如何运行
    • “异步”意味着您继续执行代码,而一段代码正在等待来自外部事物(网络请求、文件系统等)的回复,而不是坐在那里闲置等待。一切都是关于事情如何等待
  2. 由于您在等待期间继续执行代码,因此您需要通过某种方式了解请求何时实际完成。这就是 Task 对象的用途。这就是为什么任何异步方法都应该返回一个Task。如果它没有返回Task,您将永远无法知道它何时完成或如果它实际上已成功完成。

  3. 这使我们了解await 的作用。 await 关键字作用于 Task 对象。当await 作用于不完整的Task 时,方法返回。如果方法签名表明它应该返回一个Task,它返回一个Task。如果方法签名是void,则不会返回任何内容 - 但该方法仍会返回。该方法的其余部分被注册为“延续”,这意味着它会在等待完成后运行(并且根据情况,它启动的线程何时再次可用)。

了解了这一切,让我们来看看你的程序中发生了什么:

  1. Main() 致电DOS.StartAttack()
  2. StartAttack() 运行并调用 CreateMultipleTasksAsync()
  3. CreateMultipleTasksAsync() 运行。
  4. ProcessURLAsync() 被调用。一旦发送网络请求,就会返回一个不完整的TaskTask 对象会在收到回复时告诉您。
  5. 另外两个对ProcessURLAsync() 的调用也会发生同样的情况。
  6. await download1,因为download1 是不完整的TaskCreateMultipleTasksAsync() 返回自己的不完整Task
  7. 执行返回StartAttack()
  8. await 关键字看到从CreateMultipleTasksAsync() 返回的不完整的Task 并返回。因为方法签名是void,所以它什么也不返回。
  9. 执行返回到Main()。由于那里没有其他东西可以运行,因此您的程序结束。

这里的问题是,因为StartAttack()void,所以您无法知道异步请求何时完成。要解决这个问题:

  1. 更改 StartAttack() 以返回 Task 而不是 void

  2. await DOS.StartAttack() in Main()

  3. Main()的签名改为:

public static async Task Main(string[] args)

也就是说,只要您使用的是 C# 7.1 或更高版本。那时Main 方法可以开始返回Task

Microsoft 在Asynchronous programming with async and await 上有一些写得很好的文章。它们值得一读。该链接只是第一篇文章。您可以在该页面左侧的目录中找到其余内容。

【讨论】:

  • 关于“异步!=并行”的精彩解释!
  • 感谢您的解释,我确实从并行循环和多线程开始,但它只使用了 13 个线程,所以我想到了异步,所以我可以快速触发、触发、触发但未完成直到所有人回应。我的 VS2017 C# 版本说“C# 最新的主要版本(默认)”并且在 Main 方法上有一个构建错误,说“程序不包含适合入口点的静态 Main 方法”。所以我将 C# 版本更改为最高的 7.3,然后它构建并运行。正如你前段时间所说,我有这段代码,但错误把我扔了。你如何将 C# 版本保持在最新版本,因为它不是
  • "最新的 major" 可能意味着 7.0。尝试选择“最新的次要版本”。
  • 是的,最新的次要版本有效。甚至没有意识到我必须保持更新以为它会随着 C# 在 Windows 更新中使用 .NET 进行更新而更新。干杯现在已经排序,将任务响应放入数组/集合中以收集每个异步 http 请求的响应,这样我就可以收集所有状态代码/响应时间等。我知道线程的最大值可以是产生并且在TCP级别上对并发http请求没有限制吗?也不会阻止此测试应用程序正常工作。 Twitter Rush 将是一个很好的测试来自 1 个链接的 100 个机器人
猜你喜欢
  • 2011-08-06
  • 1970-01-01
  • 1970-01-01
  • 2020-11-23
  • 1970-01-01
  • 1970-01-01
  • 2017-06-27
  • 1970-01-01
相关资源
最近更新 更多