使用Await运行并行HTTP请求



所以这是从>[在C#中使用并行处理来测试站点承受DDOS 的能力

我用这篇MSKB文章作为我的例子的基础,只是我不想通过点击按钮来运行它,而是一个控制台脚本,它只是启动并启动"Attack"方法,该方法循环通过同一URL的X no,并在执行时返回。这个来自MS的示例只向Microsoft URLS发出了3个请求。

为了让它更像KB文章,我刚刚用控制台应用程序运行时调用的Main程序替换了按钮点击,该程序反过来调用Attack((,这是ONLY(目前只是试图获得3个URLS(。在实际的代码中,我有一个循环,但我需要让第一部分工作起来,这样我才能理解我做错了什么。

然而,当我在命令提示符下运行它时,我得到的只是…

C:UsersXXX>"C:UsersXXXDocumentsVisual Studio 2017ProjectsDOSBotDOSBot
binReleaseDOSBot.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("rnrnTotal bytes returned:  {total}rn");
}
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。

异步编程有几个重要的原则需要理解:

  1. 异步!=平行的您的代码中没有任何"并行"的内容。

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

  3. 这就引出了await的作用。await关键字作用于Task对象。当await作用于不完整的Task时,方法返回。如果方法签名指示它应该返回Task,那么它将返回Task。如果方法签名是void,则不会返回任何内容,但方法仍然返回。该方法的其余部分被注册为"continuation",这意味着它将在等待完成后运行(根据情况,它启动的线程何时可用(。

了解了所有这些,让我们来了解一下您的程序中发生了什么:

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

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

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

  2. Main()中的await DOS.StartAttack()

  3. Main()的签名更改为:

public static async Task Main(string[] args)

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

微软有一些写得很好的文章,介绍异步和等待异步编程。它们值得一读。这个链接只是第一篇文章。你会在那页左边的目录中找到其余的。

最新更新