問題描述
我正在開發一個多線程應用程序,其中一個線程充當從客戶端接收命令的 tcp 服務器.該線程使用 Boost 套接字和接受器等待客戶端連接,從客戶端接收命令,將命令傳遞給應用程序的其余部分,然后再次等待.代碼如下:
I'm working on a multithreaded application in which one thread acts as a tcp server which receives commands from a client. The thread uses a Boost socket and acceptor to wait for a client to connect, receives a command from the client, passes the command to the rest of the application, then waits again. Here's the code:
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() 返回后導致崩潰 - 我相信是因為在單例被銷毀后線程試圖訪問應用程序的日志單例.(老實說,我剛來的時候就是這樣.)
This thread spends most of its time blocked on the call to acceptor.accept()
. At the moment, the thread only gets terminated when the application exits. Unfortunately, this causes a crash after main() returns - I believe because the thread tries to access the app's logging singleton after the singleton has been destroyed. (It was like that when I got here, honest guv.)
如何在應用程序退出時干凈地關閉此線程?我讀到可以通過從另一個線程關閉套接字來中斷原始套接字上的阻塞 accept() 調用,但這似乎不適用于 Boost 套接字.我嘗試使用 Boost 異步 tcp 回顯服務器示例,但這似乎只是交換了對 acceptor::accept()
的阻塞調用來對 io_service 的阻塞調用::run()
,所以我遇到了同樣的問題:一個無法中斷的阻塞調用.有什么想法嗎?
How can I shut this thread down cleanly when it's time for the application to exit? I've read that a blocking accept() call on a raw socket can be interrupted by closing the socket from another thread, but this doesn't appear to work on a Boost socket. I've tried converting the server logic to asynchronous i/o using the Boost asynchronous tcp echo server example, but that just seems to exchange a blocking call to acceptor::accept()
for a blocking call to io_service::run()
, so I'm left with the same problem: a blocked call which I can't interrupt. Any ideas?
推薦答案
總之,有兩個選擇:
- 將代碼更改為異步(
acceptor::async_accept()
和async_read
),通過io_service::run()
在事件循環中運行code>,并通過io_service::stop()
取消. - 強制阻塞調用以較低級別的機制(例如信號)中斷.
- Change code to be asynchronous (
acceptor::async_accept()
andasync_read
), run within the event loop viaio_service::run()
, and cancel viaio_service::stop()
. - Force blocking calls to interrupt with lower level mechanics, such as signals.
我會推薦第一個選項,因為它更易于攜帶且更易于維護.要理解的重要概念是 io_service::run()
只會在有待處理的工作時阻塞.當 io_service::stop()
被調用,它會嘗試使 io_service::run()
上阻塞的所有線程盡快返回;它不會中斷同步操作,例如 acceptor::accept()
和 socket::receive()
,即使同步操作在事件循環中被調用.需要注意的是,io_service::stop()
是一個非阻塞調用,因此與 io_service::run()
上阻塞的線程同步必須使用另一個機制,例如 thread::join()
.
I would recommend the first option, as it is more likely to be the portable and easier to maintain. The important concept to understand is that the io_service::run()
only blocks as long as there is pending work. When io_service::stop()
is invoked, it will try to cause all threads blocked on io_service::run()
to return as soon as possible; it will not interrupt synchronous operations, such as acceptor::accept()
and socket::receive()
, even if the synchronous operations are invoked within the event loop. It is important to note that io_service::stop()
is a non-blocking call, so synchronization with threads that were blocked on io_service::run()
must use another mechanic, such as thread::join()
.
這是一個運行 10 秒并偵聽端口 8080 的示例:
Here is an example that will run for 10 seconds and listens to port 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;
}
在運行時,我打開了兩個連接.這是輸出:
While running, I opened two connections. Here is the output:
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.
這篇關于Boost::asio - 如何中斷被阻塞的 tcp 服務器線程?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!