boost::asio 完全断开连接

Posted

技术标签:

【中文标题】boost::asio 完全断开连接【英文标题】:boost::asio cleanly disconnecting 【发布时间】:2010-12-31 21:10:09 【问题描述】:

有时 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;
    

【问题讨论】:

你找到答案了吗? 【参考方案1】:

我认为您可能应该在拨打socket.close() 之前拨打socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec)

boost::asio documentation for basic_stream_socket::close 声明:

关于优雅关闭连接的套接字的可移植行为,请在关闭套接字之前调用 shutdown()。

这应该确保正确取消套接字上的任何挂起操作,并在调用 socket.close 之前刷新所有缓冲区。

【讨论】:

我遇到了与 Fire Lancer 完全相同的问题,这为我解决了,谢谢。这应该是公认的答案。【参考方案2】:

我已经尝试使用 close() 方法和 shutdown() 方法来做到这一点

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

关机方法是两者中最好的。但是,我发现使用 ASIO 套接字的析构函数是一种干净的方法,因为 ASIO 会为您处理这一切。所以你的目标是让套接字超出范围。现在,您可以使用 shared_ptr 轻松完成此操作,并将 shared_ptr 重置为新的套接字或 null。这将调用 ASIO 套接字的析构函数,生活很好。

【讨论】:

使用shared_ptr 做得很好! 这是误导。析构函数仅通过 socket.close(ec) 起作用,请参阅***.com/a/39823756/1889040。所以必须手动调用socket.shutdown才能正常关闭。我可以确认没有socket.shutdown 缓冲区(在调用关闭之前)可能不会被刷新。【参考方案3】:

也许这就是正在发生的事情:

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

我在您的读取处理程序中看到,如果有错误,您永远不会检查您的关闭数据包是否存在。也许是的。 基本上我要说的是,也许您的客户端有时能够在服务器有机会单独处理它们之前发送关闭和关闭数据包。

【讨论】:

"//error "throw" here, len always = 0 in problem cases..." 所以它总是读取头的 0 字节,即它没有读取任何数据包.. . 并且在客户端上粘贴 Sleep(500) 或其他东西并不是一个好的解决方案,因为在较慢的网络上这可能仍然不够,并且会出现明显的延迟。 也许等待来自服务器的 OK 包,或者让服务器断开连接? 但是如果客户端然后等待服务器发送断开连接消息(响应客户端断开连接消息),我只会遇到服务器断开客户端的相反问题。 ... 你能单独使用断开连接错误还是真的需要断开连接数据包?【参考方案4】:

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

【讨论】:

【参考方案5】:

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

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

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

【讨论】:

以上是关于boost::asio 完全断开连接的主要内容,如果未能解决你的问题,请参考以下文章

Boost Asio总结概述

如何在 boost asio 中设置阻塞套接字的超时时间?

boost::asio::acceptor - 在旧连接仍然打开时接受新的传入连接

如何使用 iOS Core 蓝牙在 BLE 上完全断开连接

Hazelcast 客户端与整个集群断开连接

source端在hdmi断开连接多久会停止连接