2010-06-17 59 views
17

我的客户端应用程序使用boost::asio::ip::tcp::socket连接到远程服务器。 如果应用程序失去与此服务器的连接(例如,由于服务器崩溃或正在关闭),我希望它会定期尝试重新连接,直到成功为止。如何在断开连接后干净地重新连接boost :: socket?

我需要在客户端干净地处理断开连接,整理并重复尝试重新连接?

目前我的代码中有趣的部分看起来像这样。

connect这样的:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    socket.connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     mydisconnect(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 

runIOServer()方法就是:

void MyClient::runIOService(boost::asio::io_service& io_service) 
{ 
    size_t executedCount = io_service.run(); 
    cout << "io_service: " << executedCount << " handlers executed." << endl; 
    io_service.reset(); 
} 

若有异步读取处理程序返回一个错误,那么他们只需调用这个disconnect方法:

void MyClient::mydisconnect(void) 
{ 
    boost::system::error_code errorcode; 

    if (socket.is_open()) 
    { 
     // Boost documentation recommends calling shutdown first 
     // for "graceful" closing of socket. 
     socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.shutdown error: " << errorcode.message() << endl; 
     } 

     socket.close(errorcode); 
     if (errorcode) 
     { 
      cerr << "socket.close error: " << errorcode.message() << endl; 
     }  

     // Notify the observer we have disconnected 
     myObserver->disconnected();    
    } 

..试图恩典完全断开,然后通知观察员,该观察员将以5秒的间隔开始呼叫connect(),直到重新连接。

还有什么我需要做的吗?

目前确实似乎工作。如果我终止了连接的服务器,则在读取处理程序时出现预期的"End of file"错误,并且mydisconnect()被调用时没有任何问题。

但是,当它然后尝试重新连接,并失败,我看到它报告"socket.shutdown error: Invalid argument"。这是否仅仅是因为我试图关闭一个没有读/写挂起的套接字?还是更多?

+0

如果您已经检测到连接的另一端被关闭,那么调用关机的理由是什么? – 2010-06-17 19:26:32

+0

@samm:这不推荐?我认为可能会有套接字上的挂起操作需要使用shutdown()来取消。我主要只是为了简单起见:如果我想正常断开连接,或者任何异步操作返回错误,则会调用相同的'mydisconnect()'方法。 – GrahamS 2010-06-18 08:09:04

+0

我不确定它是否被推荐。待处理的数据或操作将在哪里进行?连接的另一端不在那里。 – 2010-06-18 11:52:34

回答

22

您每次重新连接时都需要创建一个新的boost::asio::ip::tcp::socket。最简单的方法是使用boost::shared_ptr来分配堆上的套接字(如果你的套接字完全被封装在类中,你也可以使用scoped_ptr)。例如:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Attempt connection 
    // socket is of type boost::shared_ptr<boost::asio::ip::tcp::socket> 
    socket.reset(new boost::asio::ip::tcp::socket(...)); 
    socket->connect(server_endpoint, errorcode); 
    // ... 
} 

然后,当mydisconnect被调用时,你可以解除分配插座:

void MyClient::mydisconnect(void) 
{ 
    // ... 
    // deallocate socket. will close any open descriptors 
    socket.reset(); 
} 

你看到的错误可能是操作系统后您清理文件描述符的结果”我叫close。当您拨打close,然后在同一个套接字上尝试使用connect时,可能是尝试连接无效的文件描述符。此时,根据您的逻辑,您应该看到一条以“连接失败:...”开头的错误消息,但您随后调用mydisconnect,该错误消息可能试图在无效文件描述符上调用shutdown。恶性循环!

+0

试图阻止可移植性从来就不是一个好主意,尤其是当你甚至不知道目标操作系统或用于开发的操作系统时,这一点! – Geoff 2010-06-18 22:20:48

+0

@Geoff:这是一个公平的观点......我编辑了我的回复。 – bjlaub 2010-06-18 23:06:14

+0

感谢这种方法对我很好。我修改了connect,总是创建一个新的套接字,然后像你所描述的那样在'shared_ptr'上调用'.reset'。如果连接尝试失败,我只需调用'socket-> close()'并将它留在那里。我离开了'disconnect'方法,就像使用*礼貌*'关机'调用一样。 – GrahamS 2010-06-25 11:37:25

1

我在过去使用Boost.Asio做过类似的事情。我使用异步方法,因此重新连接通常会让现有的ip :: tcp :: socket对象超出范围,然后为调用async_connect创建一个新对象。如果async_connect失败,我使用一个定时器休眠一下然后重试。

+0

谢谢,所以当你检测到一个错误时,你只需调用'socket.close()',然后创建一个新的? – GrahamS 2010-06-18 08:15:41

+0

当ip :: tcp :: socket对象超出作用域时,描述符被关闭。 – 2010-06-18 11:53:01

4

为了清楚这里的目的是我用最后的办法(但这是基于bjlaub的回答,所以请给任何upvotes他):

我宣布socket成员作为scoped_ptr

boost::scoped_ptr<boost::asio::ip::tcp::socket> socket; 

然后我修改了connect方法是:

bool MyClient::myconnect() 
{ 
    bool isConnected = false; 

    // Create new socket (old one is destroyed automatically) 
    socket.reset(new boost::asio::ip::tcp::socket(io_service)); 

    // Attempt connection 
    socket->connect(server_endpoint, errorcode); 

    if (errorcode) 
    { 
     cerr << "Connection failed: " << errorcode.message() << endl; 
     socket->close(); 
    } 
    else 
    { 
     isConnected = true; 

     // Connected so setup async read for an incoming message. 
     startReadMessage(); 

     // And start the io_service_thread 
     io_service_thread = new boost::thread(
      boost::bind(&MyClient::runIOService, this, boost::ref(io_service))); 
    } 
    return (isConnected) 
} 
0

我已经试用过close()方法和关机方法而他们只是为了让我感到棘手。 Close()可以抛出一个错误,你需要捕捉并且是做你想做的事情的粗鲁方法:)和shutdown()似乎是最好的,但是在多线程软件上,我发现它可能很繁琐。所以最好的办法就像Sam说的那样让它超出范围。如果套接字是类的成员,则可以1)重新设计,以便类使用“连接”对象来封装套接字并让它超出范围或2)将其包装在智能指针中并重置智能指针。如果你使用boost,包括shared_ptr便宜并且像魅力一样工作。从来没有一个套接字清理问题,使用shared_ptr。只是我的经验。

+0

你*必须*调用'close(),''tricky'或不。否则,你有资源泄漏。对此没有任何“粗鲁”。 'shutdown()'不是'close()'的另一种选择。 – EJP 2017-03-29 04:08:15

相关问题