我正在研究一个多线程应用程序,其中一个线程充当从客户端接收命令的tcp服务器。该线程使用Boost套接字和acceptor等待客户端连接,从客户端接收命令,将命令传递给应用程序的其余部分,然后再次等待。这是代码:
void ServerThreadFunc()
{
using boost::asio::ip::tcp;
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port_no));
for (;;)
{
// listen for command connection
tcp::socket socket(io_service);
acceptor.accept(socket);
// connected; receive command
boost::array<char,256> msg_buf;
socket.receive(boost::asio::buffer(msg_buf));
// do something with received bytes here
}
}
此线程在调用acceptor.accept()
时大部分时间都被阻止。此时,线程仅在应用程序退出时终止。不幸的是,这会在main()返回后导致崩溃 - 我相信因为线程在单例被销毁后尝试访问应用程序的日志单例。 (当我到这里时,就像那个,诚实的guv。)
当应用程序退出时,如何干净地关闭此线程?我已经读过可以通过从另一个线程关闭套接字来中断原始套接字上的阻塞accept()调用,但这似乎不适用于Boost套接字。我已经尝试使用Boost asynchronous tcp echo server example将服务器逻辑转换为异步i / o,但这似乎只是阻止调用acceptor::accept()
来阻止对io_service::run()
的调用,所以我'我留下了同样的问题:一个我无法打断的被叫电话。有什么想法吗?
答案 0 :(得分:28)
简而言之,有两种选择:
acceptor::async_accept()
和async_read
),通过io_service::run()
在事件循环中运行,并通过io_service::stop()
取消。我会推荐第一个选项,因为它更可能是便携式的,更容易维护。要理解的重要概念是,只要有待处理的工作,io_service::run()
就会阻止。调用io_service::stop()
时,它会尝试使io_service::run()
上阻止的所有线程尽快返回;它不会中断同步操作,例如acceptor::accept()
和socket::receive()
,即使在事件循环中调用同步操作也是如此。请务必注意io_service::stop()
是非阻塞调用,因此与io_service::run()
上阻止的线程同步必须使用其他机制,例如thread::join()
。
这是一个将运行10秒并侦听端口8080的示例:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/thread.hpp>
#include <iostream>
void StartAccept( boost::asio::ip::tcp::acceptor& );
void ServerThreadFunc( boost::asio::io_service& io_service )
{
using boost::asio::ip::tcp;
tcp::acceptor acceptor( io_service, tcp::endpoint( tcp::v4(), 8080 ) );
// Add a job to start accepting connections.
StartAccept( acceptor );
// Process event loop.
io_service.run();
std::cout << "Server thread exiting." << std::endl;
}
void HandleAccept( const boost::system::error_code& error,
boost::shared_ptr< boost::asio::ip::tcp::socket > socket,
boost::asio::ip::tcp::acceptor& acceptor )
{
// If there was an error, then do not add any more jobs to the service.
if ( error )
{
std::cout << "Error accepting connection: " << error.message()
<< std::endl;
return;
}
// Otherwise, the socket is good to use.
std::cout << "Doing things with socket..." << std::endl;
// Perform async operations on the socket.
// Done using the socket, so start accepting another connection. This
// will add a job to the service, preventing io_service::run() from
// returning.
std::cout << "Done using socket, ready for another connection."
<< std::endl;
StartAccept( acceptor );
};
void StartAccept( boost::asio::ip::tcp::acceptor& acceptor )
{
using boost::asio::ip::tcp;
boost::shared_ptr< tcp::socket > socket(
new tcp::socket( acceptor.get_io_service() ) );
// Add an accept call to the service. This will prevent io_service::run()
// from returning.
std::cout << "Waiting on connection" << std::endl;
acceptor.async_accept( *socket,
boost::bind( HandleAccept,
boost::asio::placeholders::error,
socket,
boost::ref( acceptor ) ) );
}
int main()
{
using boost::asio::ip::tcp;
// Create io service.
boost::asio::io_service io_service;
// Create server thread that will start accepting connections.
boost::thread server_thread( ServerThreadFunc, boost::ref( io_service ) );
// Sleep for 10 seconds, then shutdown the server.
std::cout << "Stopping service in 10 seconds..." << std::endl;
boost::this_thread::sleep( boost::posix_time::seconds( 10 ) );
std::cout << "Stopping service now!" << std::endl;
// Stopping the io_service is a non-blocking call. The threads that are
// blocked on io_service::run() will try to return as soon as possible, but
// they may still be in the middle of a handler. Thus, perform a join on
// the server thread to guarantee a block occurs.
io_service.stop();
std::cout << "Waiting on server thread..." << std::endl;
server_thread.join();
std::cout << "Done waiting on server thread." << std::endl;
return 0;
}
跑步时,我打开了两个连接。这是输出:
Stopping service in 10 seconds... Waiting on connection Doing things with socket... Done using socket, ready for another connection. Waiting on connection Doing things with socket... Done using socket, ready for another connection. Waiting on connection Stopping service now! Waiting on server thread... Server thread exiting. Done waiting on server thread.
答案 1 :(得分:4)
当您收到要退出的活动时,您可以拨打acceptor.cancel()
,这将取消待处理的接受(错误代码为operation_canceled
)。在某些系统上,您也可能必须close()
接受者也是安全的。
答案 2 :(得分:3)
如果涉及到它,您可以在localhost上打开它的临时客户端连接 - 这将唤醒它。您甚至可以向它发送一条特殊消息,以便您可以从酒吧关闭服务器 - 应该有一个应用程序:)
答案 3 :(得分:0)
只需使用本机句柄和SHUT_RD选项调用shutdown,即可取消现有的receive(accept)操作。
答案 4 :(得分:0)
接受的答案并不完全正确。 事实@JohnYu正确回答。
使用ASIO的阻塞API非常类似于使用ASIO库包装在其类中的BSD套接字API。
问题是boost::asio::ip::tcp::acceptor
类不提供shutdown()功能,因此您必须使用“旧”套接字API来实现。
补充说明::请确保在使用该线程的所有线程退出之前,不删除acceptor
,socket
和io_service
。在以下代码中,std::shared_ptr
用于使共享资源保持活动状态,因此ApplicationContext
类的用户可以删除ApplicationContext
对象,并避免 SEGFAULT崩溃。
附加说明:注意增强文档,有重载的方法会引发异常,而某些方法会返回错误代码。原始Poster的代码使用acceptor->accept(socket);
而不使用try / catch,这将导致程序退出而不是正常的线程例程退出和清除。
这是解决方案说明:
#include <unistd.h> // include ::shutdown() function
// other includes ...
using boost::asio::ip::tcp;
using boost::asio::io_service;
class ApplicationContext {
// Use shared pointer to extend life of resources afer ApplicationContext is deleted
// and running threads can still keep using shared resources
std::shared_ptr<tcp::acceptor> acceptor;
std::shared_ptr<io_service> ioservice;
// called `ServerThreadFunc` in question code example
void AcceptLoopThreadRoutine(int port_no) {
ioservice = std::make_shared<io_service>();
acceptor = std::make_shared<tcp::acceptor>(*ioservice, tcp::endpoint(tcp::v4(), port_no));
try {
for (;;) {
// listen for client connection
tcp::socket socket(*ioservice);
// Note boost::system::system_error is raised when using this overload
acceptor->accept(socket);
// connected receive some data ...
// // boost::array<char,256> msg_buf;
// // socket.receive(boost::asio::buffer(msg_buf));
// do something with received bytes here
}
} catch(std::exception const & exception) {
// boost::system::system_error here indicates clean exit ;)
}
}
void StopAcceptThread() {
if(acceptor) {
// boost::asio::ip::tcp::acceptor does not have shutdown() functionality
// exposed, so we need to do it with this low-level approach
int shutdown_status = shutdown(acceptor->native_handle(), SHUT_RDWR);
}
}
};
还请注意,使用信号来取消阻止接受线程是非常讨厌的实现,并且 localhost上的临时客户端连接来取消阻止接受线程非常尴尬。
这里的ASIO可帮助您在带有回调的单线程中完成所有操作。如果您混合使用线程而ASIO很有可能是您的设计不佳。
附加说明:请勿混淆shutdown()
和close()
。某些系统可能允许您在接受套接字上使用close()
来解除阻止接受循环,但这不是可移植的。