WCF, transferts Streamed, IIS6 et IIS7 HTTP KeepAlive

This post is available in english.

Il n'y pas très longtemps, je travaillais sur un problème d'un client qui avait une exception inhabituelle levée par les sockets dans un client WCF, lui même se connectant à un service WCF hebergé dans un IIS6.

Pour faire une histoire courte, si vous utilisez WCF avec .NET 3.5, ainsi que les transferts de type "Streamed" avec un service hébergé dans IIS6, et que vous faites un très grand nombre de transferts dans un temps très court, désactivez la fonction KeepAlive du site web, ou passez à IIS7. Avec IIS6, la performance globale sera légèrement moindre, mais le site fonctionnera plus longtemps (sans appel de support client).

Toujours avec moi ? :) Si vous avez un peu plus de temps pour lire, voici quelques détails à propos de ce problème...

Le déploiement est assez simple : Un client WCF qui envoie un "Stream" vers un service WCF pour lequel le "transferMode" est "Streamed". Cela permet de transférer un important volume d'information en utilisant du "vrai" streaming, ce qui veut dire que le client va écrire dans une instance de System.IO.Stream, et dans le même temps, le serveur lis de son coté dans un autre System.IO.Stream. Les données n'ont pas besoin d'être transférées d'un seul coup pour être traitées par le serveur, comme dans une communication SOAP classique. J'utilise pour cela des deux cotés le basicHttpBinding.

Le problème surviens lorsque plus de 15000 requêtes de transfert de "Stream" ont été effectuées, et l'exception suivante est levée :

System.ServiceModel.CommunicationException: Could not connect to http://server/streamtest/StreamServiceTest.Service1.svc.
TCP error code 10048: Only one usage of each socket address (protocol/network address/port) is normally permitted 10.0.0.1:80. 
---> System.Net.WebException: Unable to connect to the remote server
---> System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted 10.0.0.1:80


C'est une exception plutôt commune, et elle est trouvée généralement dans une application serveur qui essaye de s'associer à un port TCP, mais ne peux pas le faire soit parce qu'une autre application l'utilise, ou bien parce que l'application n'utilise pas l'option SO_REUSEADDR et que le port a été fermé récemment.

En revanche, ce qui est inhabituel, c'est que l'exception est levée coté client et non coté serveur !

Après quelques netstat -an, j'ai trouvé un nombre impressionnant de sockets qui étaient en attente avec l'état suivant :

TCP    the.client:50819     the.server:80          TIME_WAIT

Il y avait quelque chose comme 15000 lignes comme celles la, avec des nombres s'incrémentant pour le port local. Cela a provoqué une pénurie de ports TCP entrants, d'où l'erreur reportée par les sockets. Cet état est cependant normal, et c'est un état attendu, mais il est plus communément trouvé sur un serveur et bien moins sur un client.

Cela ne pourrait vouloir dire qu'une chose, considérant que IIS6.0 est compatible HTTP/1.1: WCF demande que la connexion soit fermée à la fin d'un transfert "Streamed", alors qu'on pourrait penser qu'une connection en mode KeepAlive serait utilisée.

Wireshark étant mon ami, j'ai commencé à analyser le dialogue entre IIS6 et mon application cliente :

POST /streamtest/StreamServiceTest.Service1.svc HTTP/1.1
MIME-Version: 1.0
Content-Type: multipart/related; type="application/xop+xml";start="<http://tempuri.org/0>";boundary="uuid:41d2cf74-aaa6-4a80-a6c4-0ec37692a437+id=1";start-info="text/xml"
SOAPAction: "http://tempuri.org/IService1/Operation1"
Host: the.server
Transfer-Encoding: chunked
Expect: 100-continue
Connection: Keep-Alive

Ce qui veut dire qu'en fait, le client WCF demande bien que la connexion reste ouverte, contrairement à ce que les sockets fermés laissent penser. Le serveur répond ceci :

HTTP/1.1 100 Continue

Le transfert en mode stream s'opère, puis le client reçoit la réponse SOAP, puis ceci :

HTTP/1.1 200 OK
Date: Sat, 11 Jul 2009 01:40:16 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Connection: close
MIME-Version: 1.0
Transfer-Encoding: chunked
Cache-Control: private

Cela veut dire que c'est IIS6.0 ou le handler WCF qui force la fermeture de la connexion sur cette dernière réponse. La non plus, cette réponse n'est en soi pas anormale, puisque le KeepAlive peut être ignoré par le serveur et fermer la connexion malgré tout.

Le plus étrange dans tout cela, et vraiment à tout hazard, j'ai tenté de désactiver l'option KeepAlive du site web IIS6.0... Et à partir de ce moment la, toutes les connexions se sont fermées proprement sur le client !

Après une petite analyse du dialogue entre le client et le serveur, j'ai noté deux différences entre les deux états du KeepAlive :

  1. Le contenu de la dernière réponse HTTP du serveur est composé de deux en-têtes "Connection: close", ce qui pourrait vouloir dire un par le handler WCF et un autre par IIS. Je ne suis pas certain que la répétition des headers soit interdite dans la RFC. Il faudra que je la relise pour en être certain.
  2. Il semblerait que l'ordre des FIN/ACK, ACK est différent sur la fermeture de la connexion TCP, mais je ne suis pas certain de l'impact. Le client et le serveur envoient des paquets FIN de l'autre coté, ce qui est probablement le résultat d'un Socket.Close de chaque coté. Le problème vient peut-être de la.

Mais par la suite j'ai trouvé quelque chose de plus intéressant : Tout fonctionne avec IIS7 ! Et encore mieux, le statut KeepAlive est pris en compte par le serveur. Cela veut évidemment dire que le service web est bien plus performant avec IIS7 qu'il ne l'est avec IIS6, puisqu'une seule connexion TCP est ouverte pour les 15000 appels, ce qui est plutôt bon. Dommage que mon client ne puisse pas passer à IIS7 pour le moment...

Il semble également que WCF ne se comporte pas de la même manière avec IIS7 qu'avec IIS6, puisqu'au niveau TCP, il n'y a que le client qui termine la connexion en envoyant un TCP/FIN alors même que le KeepAlive serveur est désactivé.

Je vais probablement poster ce problème sur Microsoft Connect sous peu, mais je ne sais pas exactement ou se situe la source du problème, soit IIS6, le client WCF ou le handler serveur WCF. Dans tous les cas, il y a vraiment un problème.

Publié samedi 11 juillet 2009 15:26 par jay
Classé sous , ,
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires


Les 10 derniers blogs postés

- SharePoint 2013: Préparation de la migration - Création des site Templates dans 2010 et 2013 par Blog Technique de Romelard Fabrice le 08-20-2014, 16:31

- [ #Yammer ] How to change interface language ? Comment changer la langue de l’interface ? par Le blog de Patrick [MVP SharePoint] le 08-20-2014, 14:21

- Onedrive Sync Engine Host : CPU à 100% par Le petit blog de Pierre / Pierre's little blog le 08-06-2014, 22:22

- SharePoint : Bug sur la gestion des permissions et la synchronisation Office par Blog Technique de Romelard Fabrice le 07-10-2014, 11:35

- SharePoint 2007 : La gestion des permissions pour les Workflows par Blog Technique de Romelard Fabrice le 07-08-2014, 11:27

- TypeMock: mock everything! par Fathi Bellahcene le 07-07-2014, 17:06

- Coding is like Read par Aurélien GALTIER le 07-01-2014, 15:30

- Mes vidéos autour des nouveautés VS 2013 par Fathi Bellahcene le 06-30-2014, 20:52

- Recherche un passionné .NET par Tkfé le 06-16-2014, 12:22

- [CodePlex] Projet KISS Workflow Foundation lancé par Blog de Jérémy Jeanson le 06-08-2014, 22:25