2009-09-16 13 views
3

Je suis en train d'écrire un client XMLRPC en C++ destiné à parler à un serveur XMLRPC python.Erreur d'utilisation du socket lors de la réutilisation des sockets

Malheureusement, à ce moment, le serveur XMLRPC python est que peut mettre en service une requête sur une connexion, il arrête, j'ai découvert cela grâce à la réponse de mhawke à ma précédente question au sujet d'un related subject

En raison de ceci, je dois créer une nouvelle connexion de socket à mon serveur de python chaque fois que je veux faire une demande de XMLRPC. Cela signifie la création et la suppression de beaucoup de sockets. Tout fonctionne bien, jusqu'à ce que j'approche ~ 4000 demandes. À ce stade, je reçois erreur de socket 10048, Socket in use.

J'ai essayé de dormir le thread pour laisser winsock corriger ses descripteurs de fichier, une astuce qui fonctionnait quand un de mes clients python avait un problème identique, en vain. J'ai essayé les suivantes

int err = setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,(char*)TRUE,sizeof(BOOL)); 

sans succès.

J'utilise Winsock 2.0, WSADATA :: iMaxSockets ne devrait pas entrer en jeu, et de toute façon, j'ai vérifié et son ensemble à 0 (je suppose que cela signifie l'infini)

4000 demandes ne pas ressemble à un nombre exagéré de demandes à faire au cours d'une application. Est-il possible d'utiliser SO_KEEPALIVE côté client pendant que le serveur se ferme et rouvre?

Ai-je complètement oublié quelque chose?

Répondre

0

Fermez-vous les prises après l'avoir utilisé?

+0

oui, après chaque requête, j'appelle closesocket(). J'ai vérifié, et ceci arrive même la dernière demande avant que l'erreur se produise, ainsi la douille n'est pas laissée ouverte – DanJ

1

Mise à jour:

J'ai jeté cela dans le code et il semble fonctionner maintenant.

if(::connect(s_, (sockaddr *) &addr, sizeof(sockaddr))) 
    { 
    int err = WSAGetLastError(); 
    if(err == 10048) //if socket in user error, force kill and reopen socket 
    { 
     closesocket(s_); 
     WSACleanup(); 
     WSADATA info; 
     WSAStartup(MAKEWORD(2,0), &info); 
     s_ = socket(AF_INET,SOCK_STREAM,0); 
     setsockopt(s_,SOL_SOCKET,SO_REUSEADDR,(char*)&x,sizeof(BOOL)); 
    } 
    } 

En fait, si vous rencontrez l'erreur 10048 (prise en cours d'utilisation), vous pouvez simplement fermer le socket, appelez le nettoyage et redémarrez WSA, la réinitialisation de la prise et son sockopt

(la dernière sockopt peut ne pas être nécessaire)

i doivent avoir été portés disparus le WSACleanup/WSAStartup appelle avant, parce que closesocket() et socket() ont été sans aucun doute être appelé

cette erreur se produit qu'une fois tous les appels 4000ish.

Je suis curieux de savoir pourquoi cela peut être, même si cela semble le réparer. Si quelqu'un a des commentaires sur le sujet, je serais très curieux de l'entendre

11

Le problème est provoqué par les sockets qui traînent dans l'état TIME_WAIT qui est entré une fois que vous fermez le socket du client. Par défaut, le socket restera dans cet état pendant 4 minutes avant d'être réutilisable. Votre client (éventuellement aidé par d'autres processus) les consomme tous dans une période de 4 minutes. Voir this answer pour une bonne explication et une solution possible sans code.

Windows alloue dynamiquement les numéros de port dans la plage 1024-5000 (3977 ports) lorsque vous ne liez pas explicitement l'adresse de socket.Ce code Python illustre le problème:

import socket 
sockets = [] 
while True: 
    s = socket.socket() 
    s.connect(('some_host', 80)) 
    sockets.append(s.getsockname()) 
    s.close() 

print len(sockets)  
sockets.sort() 
print "Lowest port: ", sockets[0][1], " Highest port: ", sockets[-1][1] 
# on Windows you should see something like this... 
3960 
Lowest port: 1025 Highest port: 5000 

Si vous essayez d'exécuter cette immeditaely encore, il devrait échouer très rapidement puisque tous les ports dynamiques sont à l'état TIME_WAIT.

Il y a quelques façons de contourner cela:

  1. Gérer vos propres affectations de port et utilisation bind() pour lier explicitement votre socket client à un port spécifique que vous incrémenter chaque fois que votre créer une socket . Vous aurez toujours pour gérer le cas où un port est déjà utilisé, mais vous ne serez pas limité aux ports dynamiques. par exemple.

    port = 5000 
    while True: 
        s = socket.socket() 
        s.bind(('your_host', port)) 
        s.connect(('some_host', 80)) 
        s.close() 
        port += 1 
    
  2. Fiddle avec l'option prise SO_LINGER . J'ai trouvé que ce fonctionne parfois sous Windows (bien que pas exactement pourquoi): s.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, 1)

  3. Je ne sais pas si cela vous aidera dans votre application, cependant, il est possible d'envoyer plusieurs demandes XMLRPC sur la même connexion en utilisant la méthode multicall. Fondamentalement cela vous permet d'accumuler plusieurs demandes, puis les envoyer tous à la fois. Vous ne recevrez aucune réponse de tant que vous n'aurez pas envoyé les demandes accumulées, vous pouvez donc considérer comme un traitement de lot - est-ce que cela correspond à votre conception d'application?

Questions connexes