2010-01-02 23 views
22

A veces boost :: asio parece desconectarse antes de que yo quiera, es decir, antes de que el servidor maneje correctamente la desconexión. No estoy seguro de cómo esto es posible porque el cliente parece pensar que envió el mensaje por completo, pero cuando el servidor emite el error, ni siquiera lee el encabezado del mensaje ... Durante las pruebas, esto solo ocurre 1 de cada 5 veces, el el servidor recibe el mensaje de cierre del cliente y desconecta el cliente de forma limpia.boost :: asio cleanly disconnecting

El error: "Una conexión existente forzosamente fue cerrada por el host remoto"

El cliente desconectar: ​​

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); 
} 

El servidor:

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; 
    } 
} 
+0

¿Alguna vez encontró una respuesta a esto? – GrahamS

Respuesta

21

Creo que probablemente deba llamar al socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec) antes de llamar a socket.close().

Los boost::asio documentation for basic_stream_socket::close estados:

For portable behaviour with respect to graceful closure of a connected socket, call shutdown() before closing the socket.

Esto debería garantizar que cualquier operación pendiente del zócalo están debidamente cancelados y cualquier búfer se vacían antes de la llamada a Socket.close.

+0

He tenido exactamente el mismo problema que Fire Lancer, y esto lo resolvió para mí, gracias. Esta debería ser probablemente la respuesta aceptada. – Silverlan

5

Tal vez esto es lo que está sucediendo :

  • Paquete de desconexión de envío del cliente
  • El cliente cierra el socket
  • Se llama al controlador de lectura del servidor, pero hay un error asociado con el paquete de apagado porque el socket ya está cerrado.

Veo en sus manejadores de lectura, si hay un error, nunca verifica si su paquete de apagado está allí. Tal vez lo sea Básicamente lo que estoy diciendo es que quizás su cliente a veces puede enviar tanto el paquete de cierre como el de cierre antes de que el servidor tenga la oportunidad de procesarlos por separado.

+0

"// error" throwwn "here, len always = 0 en los casos problemáticos ..." por lo que siempre lee 0 bytes del encabezado, es decir, no ha leído ninguno del paquete ... Y guardando un Sleep (500) o algo en el cliente no es una buena solución, porque aún no siempre es suficiente en redes más lentas y es un retraso notable. –

+0

¿Quizás espere a que el servidor muestre el paquete OK o haga que el servidor se desconecte? – Macke

+0

Pero si el cliente espera a que el servidor envíe un mensaje de desconexión (en respuesta al mensaje de desconexión del cliente), terminaré con el problema opuesto al de la desconexión del servidor en el cliente .... –

3

Utilice async_write() y coloque socket.close() dentro del controlador de escritura. Esto asegurará que el paquete sea procesado por boost asio y no descuidado en el medio del proceso (debido a llamadas close()).

2

Tengo un problema muy similar. Creo que está relacionado con las conexiones de reciclaje de Windows. ¿Es lo siguiente familiar?

  • ¿recibe este error inmediatamente después de iniciar el programa, pero no después de establecer una conexión?
  • ¿El error nunca ocurre si espera más de 4 minutos antes de reiniciar su aplicación?

Las especificaciones tcp especifican que, de forma predeterminada, debe esperar cuatro minutos para la confirmación final cuando se cierra una conexión tcp. Puede ver estas conexiones en estado FIN_WAIT usando netstat. El sistema operativo Windows detecta cuando intenta conectarse al mismo sistema y toma estas conexiones parcialmente cerradas y las recicla. Su segunda invocación del programa obtiene la conexión 'cerrada' que dejó la primera ejecución. Obtiene el siguiente reconocimiento y luego realmente se cierra.

9

he tratado de hacer esto tanto con el método close() y el método de apagado()

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

El método de apagado es el mejor de los dos. Sin embargo, considero que usar el destructor del zócalo ASIO es la forma más fácil de hacerlo, ya que ASIO se encarga de todo. Entonces, tu objetivo es dejar que el socket caiga fuera del alcance. Ahora, puede hacer esto fácilmente usando un shared_ptr y reiniciando el shared_ptr a un socket nuevo o nulo. esto llamará al destructor de la toma ASIO y la vida es buena.

+0

Usar 'shared_ptr' hace el trabajo muy bien! – nabroyan

+0

Esto es engañoso. El destructor actúa solo como si fuera socket.close (ec), consulte https://stackoverflow.com/a/39823756/1889040. Por lo tanto, debe llamar manualmente a 'socket.shutdown' para cerrarlo correctamente. Puedo confirmar que sin los búferes 'socket.shutdown' (antes de la llamada de cierre) no se pueden eliminar. – scinart