2010-01-02 74 views
22

有时boost :: asio似乎在我希望之前断开连接,即在服务器正确处理断开连接之前。我不知道这是如何实现的,因为客户端似乎认为它完全发送了消息,但是当服务器发出错误时它甚至没有读取消息头......在测试期间,这种情况只发生在5次,服务器收到客户端关闭消息,并干净地断开客户端。boost :: asio干净地断开连接

错误:

客户端断开 “一个现有的连接被强行关闭远程主机”:

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

服务器:

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

你有没有找到答案? – GrahamS

回答

21

我想你应该在致电socket.close()之前打电话给socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec)

boost::asio documentation for basic_stream_socket::close状态:

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

这应确保插座上的任何挂起操作是否正确取消任何缓冲区来调用socket.close之前刷新。

+0

我和Fire Lancer的问题完全一样,这对我来说已经解决了,谢谢。这应该可以被接受的答案。 – Silverlan

5

也许这是发生了什么事:

  • 客户端发送断开包
  • 客户端关闭套接字
  • 服务器读取处理程序被调用,但由于套接字已关闭,因此存在与关闭包关联的错误。

我看到你的阅读处理程序,如果有错误,你从不检查你的关闭数据包是否在那里。也许是。 基本上我说的是,也许你的客户端有时能够在服务器有机会分别处理它们之前发送关闭和关闭包。

+0

“//错误”在这里抛出,len总是在问题情况下为0 ......所以它总是读取头的0个字节,即它没有读取任何数据包......并且坚持一个睡眠(500 )或客户端上的某些东西并不是一个好的解决方案,因为在较慢的网络上这样做可能并不总是足够的,并且是明显的延迟。 –

+0

也许等待来自服务器的OK包,或让服务器断开连接? – Macke

+0

但是,如果客户端然后等待服务器发送断开连接消息(作为对客户端断开连接消息的响应),那么我只会结束服务器在客户端断开连接的相反问题.... –

3

使用async_write()并将write.close()放入写入处理程序中。这将确保数据包由boost asio处理,并且在处理过程中不会被忽略(因为close()调用)。

2

我有一个非常类似的问题。我相信这与Windows回收连接有关。以下是否熟悉?

  • 您在启动程序时立即得到此错误,但在连接建立后不会立即生效?
  • 如果您在重新启动应用程序之前等待超过4分钟,则错误不会发生?

tcp规范指定,默认情况下,当tcp连接关闭时,它应该等待4分钟才能进行最终确认。您可以使用netstat以FIN_WAIT状态查看这些连接。 Windows操作系统检测您何时尝试连接到完全相同的系统,并采取这些部分关闭的连接并对其进行回收。您第二次调用该程序会获得第一次运行留下的“关闭”连接。它得到下一个确认,然后真正关闭。

9

我试图与这两个close()方法和shutdown()方法

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

关机方法是最好的两个来做到这一点。然而,我发现使用ASIO套接字的析构函数是干净利落的方法,因为ASIO会为您完成所有这些工作。所以你的目标是让套接字掉出范围。现在,您可以使用shared_ptr轻松完成此操作,并将shared_ptr重置为全新套接字或null。这将调用ASIO套接字的析构函数,并且生活很好。

+0

使用'shared_ptr'完成这项工作非常好! – nabroyan

+0

这是误导。析构函数只能通过socket.close(ec)进行操作,请参阅https://stackoverflow.com/a/39823756/1889040。所以你必须手动调用'socket.shutdown'来正确关闭。我可以确认没有'socket.shutdown'缓冲区(在关闭调用之前)可能不会被刷新。 – scinart