Multi-threaded HttpListener avec attente asynchrone et tâches
Est-ce un bon exemple d'un HttpListener extensible qui est multi-threadé?
Est-ce ainsi, par exemple, qu'un vrai IIS le ferait?
public class Program
{
private static readonly HttpListener Listener = new HttpListener();
public static void Main()
{
Listener.Prefixes.Add("http://+:80/");
Listener.Start();
Listen();
Console.WriteLine("Listening...");
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
}
private static async void Listen()
{
while (true)
{
var context = await Listener.GetContextAsync();
Console.WriteLine("Client connected");
Task.Factory.StartNew(() => ProcessRequest(context));
}
Listener.Close();
}
private static void ProcessRequest(HttpListenerContext context)
{
System.Threading.Thread.Sleep(10*1000);
Console.WriteLine("Response");
}
}
je cherche spécifiquement une solution évolutive qui ne repose pas sur L'IIS. Au lieu de cela, uniquement sur http.sys (qui est la classe httplistener) -- la raison pour ne pas compter sur iIS est parce que le gouvernement. la zone dans laquelle je travaille nécessite une surface d'attaque extrêmement réduite.
3 réponses
j'ai fait quelque chose de similaire à https://github.com/JamesDunne/Aardwolf et en avons fait de nombreux tests sur cette.
voir le code à https://github.com/JamesDunne/aardwolf/blob/master/Aardwolf/HttpAsyncHost.cs#L107 pour la mise en œuvre de la boucle d'événement de base.
je trouve que l'utilisation d'un Semaphore
pour contrôler combien de concurrents GetContextAsync
les requêtes sont actives est la meilleure approche. Essentiellement, la boucle principale continue de tourner jusqu'à ce que la sémaphore bloque le fil en raison du compte étant atteint. Il y aura alors N "connexion accepts" active. Chaque fois qu'une connexion est acceptée, le sémaphore est libéré et une nouvelle demande peut prendre sa place.
les valeurs initiales et max count du Sémaphore nécessitent un réglage fin, en fonction de la charge que l'on s'attend à recevoir. Il s'agit d'un équilibre délicat entre le nombre de connexions simultanées que vous attendez et les délais de réponse moyens que vos clients désirent. Des valeurs plus élevées signifient que plus de connexions peuvent être maintenues, mais avec un temps de réponse moyen beaucoup plus lent; moins de connexions seront rejetées. Des valeurs plus basses signifient que moins de connexions peuvent être maintenues, mais avec un temps de réponse moyen beaucoup plus rapide; plus de connexions seront rejetées.
j'ai trouvé, expérimentalement (sur mon matériel), que les valeurs autour de 128
permet au serveur de gérer de grandes quantités de connexions simultanées (jusqu'à 1024) à des temps de réponse acceptables. Tester avec votre propre matériel et régler vos paramètres en conséquence.
j'ai aussi constaté qu'une seule instance de WCAT n'aime pas traiter plus de 1024 connexions elle-même. Donc, si vous êtes sérieux au sujet des tests de charge, Utilisez plusieurs machines clientes avec WCAT contre votre serveur et assurez-vous de tester sur un réseau rapide par exemple 10 GbE et que les limites de votre OS ne vous ralentissent pas. Assurez-vous de tester sur Windows Server SKUs parce que les SKUs de bureau sont limités par défaut.
Résumé: La façon dont vous écrivez votre boucle d'acceptation de connexion est critique pour l'évolutivité de votre serveur.
Techniquement, vous avez raison. Pour le rendre extensible, vous voulez probablement avoir plusieurs GetContextAsync tournant en même temps (les tests de performance devaient savoir exactement combien, mais "quelques uns pour chaque noyau" est probablement la bonne réponse).
puis naturellement, comme le soulignent les commentaires; ne pas utiliser IIS signifie que vous devez être assez sérieux au sujet de la sécurité pour beaucoup de choses que IIS vous donne "gratuitement".
je sais que je suis extrêmement en retard à la fête sur ce sujet, mais j'ai publié une bibliothèque (source ici https://github.com/jchristn/WatsonWebserver) sur NuGet qui encapsule un serveur web async.