web-dev-qa-db-fra.com

boost :: asio se déconnectant proprement

Parfois, boost :: asio semble se déconnecter avant que je le veuille, c'est-à-dire avant que le serveur gère correctement la déconnexion. Je ne sais pas comment cela est possible car le client semble penser qu'il a entièrement envoyé le message, mais lorsque le serveur émet l'erreur, il ne lit même pas l'en-tête du message ... Pendant le test, cela ne se produit que peut-être 1 fois sur 5, le le serveur reçoit le message d'arrêt du client et déconnecte le client proprement.

L'erreur: "Une connexion existante a été fermée de force par l'hôte distant"

Le client se déconnectant:

void disconnect()
{
    boost::system::error_code error;
    //just creates a simple buffer with a shutdown header
    boost::uint8_t *packet = createPacket(PC_SHUTDOWN,0);
    //sends it
    if(!sendBlocking(socket,packet,&error))
    {
        //didnt get here in my tests, so its not that the write failed...
        logWrite(LOG_ERROR,"server",
            std::string("Error sending shutdown message.\n")
            + boost::system::system_error(error).what());
    }

    //actaully disconnect
    socket.close();
    ioService.stop();
}
bool sendBlocking(boost::asio::ip::tcp::socket &socket,
    boost::uint8_t *data, boost::system::error_code* error)
{
    //get the length section from the message
    boost::uint16_t len = *(boost::uint16_t*)(data - 3);
    //send it
    asio::write(socket, asio::buffer(data-3,len+3),
        asio::transfer_all(), *error);
    deletePacket(data);
    return !(*error);
}

Le serveur:

void Client::clientShutdown()
{
    //not getting here in problem cases
    disconnect();
}
void Client::packetHandler(boost::uint8_t type, boost::uint8_t *data,
    boost::uint16_t len, const boost::system::error_code& error)
{
    if(error)
    {
        //error handled here
        delete[] data;
        std::stringstream ss;
        ss << "Error recieving packet.\n";
        ss << logInfo() << "\n";
        ss << "Error: " << boost::system::system_error(error).what();
        logWrite(LOG_ERROR,"Client",ss.str());

        disconnect();
    }
    else
    {
        //call handlers based on type, most will then call startRead when
        //done to get the next packet. Note however, that clientShutdown
        //does not
        ...
    }
}



void startRead(boost::asio::ip::tcp::socket &socket, PacketHandler handler)
{
    boost::uint8_t *header = new boost::uint8_t[3];
    boost::asio::async_read(socket,boost::asio::buffer(header,3),
        boost::bind(&handleReadHeader,&socket,handler,header, 
        boost::asio::placeholders::bytes_transferred,boost::asio::placeholders::error));
}
void handleReadHeader(boost::asio::ip::tcp::socket *socket, PacketHandler handler,
    boost::uint8_t *header, size_t len, const boost::system::error_code& error)
{
    if(error)
    {
        //error "thrown" here, len always = 0 in problem cases...
        delete[] header;
        handler(0,0,0,error);
    }
    else
    {
        assert(len == 3);
        boost::uint16_t payLoadLen  = *((boost::uint16_t*)(header + 0));
        boost::uint8_t  type        = *((boost::uint8_t*) (header + 2));
        delete[] header;
        boost::uint8_t *payLoad = new boost::uint8_t[payLoadLen];

        boost::asio::async_read(*socket,boost::asio::buffer(payLoad,payLoadLen),
            boost::bind(&handleReadBody,socket,handler,
            type,payLoad,payLoadLen,
            boost::asio::placeholders::bytes_transferred,boost::asio::placeholders::error));
    }
}
void handleReadBody(ip::tcp::socket *socket, PacketHandler handler,
    boost::uint8_t type, boost::uint8_t *payLoad, boost::uint16_t len,
    size_t readLen, const boost::system::error_code& error)
{
    if(error)
    {
        delete[] payLoad;
        handler(0,0,0,error);
    }
    else
    {
        assert(len == readLen);
        handler(type,payLoad,len,error);
        //delete[] payLoad;
    }
}
29
Fire Lancer

Je pense que vous devriez probablement avoir un appel à socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec) avant l'appel à socket.close().

La documentation boost :: asio pour basic_stream_socket :: close indique:

Pour un comportement portable en ce qui concerne la fermeture élégante d'un socket connecté, appelez shutdown () avant de fermer le socket.

Cela devrait garantir que toutes les opérations en attente sur le socket sont correctement annulées et que tous les tampons sont vidés avant l'appel à socket.close.

26
GrahamS

J'ai essayé de le faire avec la méthode close () et la méthode shutdown ()

socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec)

La méthode d'arrêt est la meilleure des deux. Cependant, je trouve que l'utilisation du destructeur de la prise ASIO est la meilleure façon de le faire car ASIO s'occupe de tout pour vous. Votre objectif est donc de laisser la prise hors de portée. Maintenant, vous pouvez le faire facilement en utilisant un shared_ptr et en réinitialisant le shared_ptr sur un nouveau socket ou null. cela appellera le destructeur de la prise ASIO et la vie est bonne.

11
William Symionow

C'est peut-être ce qui se passe:

  • Le client envoie un paquet de déconnexion
  • Le client ferme le socket
  • Le gestionnaire de lecture du serveur est appelé, mais une erreur est associée au paquet d'arrêt car le socket est déjà fermé.

Je vois dans vos gestionnaires de lecture, s'il y a une erreur, vous ne vérifiez jamais si votre paquet d'arrêt est là. Peut etre c'est. Fondamentalement, ce que je dis, c'est que votre client peut parfois envoyer à la fois le paquet de fermeture et le paquet d'arrêt avant que le serveur n'ait la possibilité de les traiter séparément.

5
Chris H

Utilisez async_write () et placez socket.close () dans le gestionnaire d'écriture. Cela garantira que le paquet est traité par boost asio et n'est pas négligé au milieu du traitement (à cause des appels close ()).

4
flamemyst

J'ai un problème très similaire. Je crois que c'est lié aux connexions de recyclage de Windows. Le suivant est-il familier?

  • vous obtenez cette erreur immédiatement au démarrage du programme, mais pas une fois la connexion établie?
  • L'erreur ne se produit jamais si vous attendez plus de 4 minutes avant de redémarrer votre application?

Les spécifications tcp spécifient que par défaut, il devrait attendre quatre minutes pour l'acquittement final lorsqu'une connexion tcp est fermée. Vous pouvez voir ces connexions dans l'état FIN_WAIT à l'aide de netstat. Le système d'exploitation Windows détecte lorsque vous essayez de vous connecter exactement au même système, prend ces connexions partiellement fermées et les recycle. Votre deuxième invocation du programme obtient la connexion "fermée" laissée par la première exécution. Il obtient le prochain accusé de réception, puis se ferme vraiment.

3
Jay