我的客户端应用程序使用boost::asio::ip::tcp::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();
}
..试图优雅地断开然后通知观察者,观察者将以五秒的间隔开始调用connect()
,直到它重新连接。
还有什么我需要做的吗?
目前,似乎可以正常工作。如果我终止它所连接的服务器,我的读取处理程序会出现预期的"End of file"
错误,并且mydisconnect()
被调用而没有任何问题。
但是当它尝试重新连接并失败时,我会看到它报告"socket.shutdown error: Invalid argument"
。这只是因为我试图关闭一个没有读/写挂起的套接字吗?还是更多的东西?
答案 0 :(得分:26)
每次重新连接时都需要创建一个新的boost::asio::ip::tcp::socket
。执行此操作的最简单方法可能是使用boost::shared_ptr
在堆上分配套接字(如果套接字完全封装在类中,则可能还可以使用scoped_ptr
。) E.g:
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
。恶性循环!
答案 1 :(得分:7)
为了清楚起见,这是我使用的最终方法(但这是基于bjlaub的答案,所以请给他任何支持):
我将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)
}
注意:此问题最初是在2010年提出并回答的,但如果您现在使用的是C ++ 11或更高版本,那么std::unique_ptr
通常会比boost::scoped_ptr
更好地选择
答案 2 :(得分:2)
我过去曾使用Boost.Asio做过类似的事情。我使用异步方法,因此重新连接通常会让我现有的ip :: tcp :: socket对象超出范围,然后为调用async_connect创建一个新对象。如果async_connect失败,我会使用一个计时器睡一会儿,然后重试。
答案 3 :(得分:0)
我已经尝试了close()方法和关闭方法,它们对我来说只是很棘手。 Close()可以抛出你需要捕获的错误,并且是你想做的事情的粗鲁方式:)和shutdown()似乎是最好的,但在多线程软件上,我发现它可能很挑剔。因此,正如萨姆所说,最好的办法就是让它超出范围。如果套接字是该类的成员,您可以1)重新设计,以便该类使用&#39;连接&#39;对象包装套接字并让它超出范围或2)将其包装在智能指针中并重置智能指针。如果你使用boost,包括shared_ptr很便宜,就像魅力一样。从未使用shared_ptr执行套接字清理问题。只是我的经历。
答案 4 :(得分:0)
自C ++ 11起,您可以编写:
decltype(socket)(std::move(socket));
// reconnect socket.
上面创建了一个套接字类型移动的本地实例,从套接字构造它。
在下一行之前,未命名的本地实例被破坏,从而使套接字处于“由io_service全新构建”状态。