我正在开发客户端/服务器 TCP 应用程序。它在 Windows 下工作得很好,但我在 Linux 下遇到了问题:当与客户端建立新连接时,服务器没有收到通知(很少有,但大多数情况下都没有)。
我能够通过一个非常简单的程序来隔离问题。该程序创建并运行一个服务器(带有它自己的
boost::asio::io_service
)接受新连接,然后从客户端建立连接(也带有它自己的 boost::asio::io_service
,就像它在单独的应用程序中的行为一样)。
这是代码:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <assert.h>
using boost::asio::ip::tcp;
std::shared_ptr<tcp::socket> m_nextConnection;
std::atomic_bool m_continueServerThread = true;
void server_thread_func(boost::asio::io_service* service)
{
// http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/io_service.html#boost_asio.reference.io_service.effect_of_exceptions_thrown_from_handlers
while (m_continueServerThread) {
try {
service->run();
// don't break, keep looping, else we may exit before a connection is actually established
//break; // exited normally
}
catch (std::exception const& e) {
std::cerr << "[eventloop] error: " << e.what();
}
catch (...) {
std::cerr << "[eventloop] unexpected error";
}
}
}
void on_accept_connection(std::error_code ec)
{
if (!ec)
{
std::cout << "SERVER ACCEPTED CONNECTION" << std::endl;
}
else
{
std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
}
}
void do_accept_connection(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& service)
{
m_nextConnection = std::shared_ptr<tcp::socket>(new boost::asio::ip::tcp::socket(service));
acceptor.async_accept(
*m_nextConnection, boost::bind(on_accept_connection, boost::asio::placeholders::error));
}
int main( int argc, char* argv[] )
{
auto endpoint = boost::asio::ip::tcp::endpoint{ boost::asio::ip::address::from_string("127.0.0.1"), 1900 };
try
{
// start server:
boost::asio::io_service IOServiceServer;
boost::thread serverThread( boost::bind(server_thread_func,&IOServiceServer) );
boost::this_thread::sleep(boost::posix_time::milliseconds(500));
boost::asio::ip::tcp::acceptor m_acceptor{ IOServiceServer, endpoint };
// dunno if this is needed or not here
//m_acceptor.set_option(tcp::acceptor::reuse_address(true));
do_accept_connection(m_acceptor, IOServiceServer);
boost::this_thread::sleep(boost::posix_time::milliseconds(500));
// start client:
boost::asio::io_service IOServiceClient;
tcp::socket socket(IOServiceClient);
std::cout << "Connecting socket..." << std::endl;
socket.connect(endpoint);
std::cout << "Connected socket" << std::endl;
boost::this_thread::sleep(boost::posix_time::milliseconds(500));
// stop/close client:
IOServiceClient.stop();
socket.close();
// stop server:
IOServiceServer.stop();
m_continueServerThread = false;
serverThread.join();
return 0;
}
catch (std::exception const& e) {
std::cerr << "error: " << e.what();
}
catch (...) {
std::cerr << "unexpected error";
}
return 1;
}
在Windows下运行,显示:
Connecting socket...
Connected socket
SERVER ACCEPTED CONNECTION
在Linux下运行,这只显示:
Connecting socket...
Connected socket
如您所见,
on_accept_connection
没有被调用,因此服务器不会收到建立新连接的通知。
我做错了什么吗?
编辑:也尝试过不使用 m_nextConnection:
void on_accept_connection(std::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::cout << "SERVER ACCEPTED CONNECTION" << std::endl;
}
else
{
std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
}
}
void do_accept_connection(boost::asio::ip::tcp::acceptor& acceptor, boost::asio::io_service& service)
{
acceptor.async_accept(service, [&](const std::error_code& ec, tcp::socket newSocket) {
on_accept_connection(ec, std::move(newSocket));
});
}
并不能解决问题!
服务器线程是从这里修改的。评论很说明问题:
// don't break, keep looping, else we may exit before a connection is actually established
这不是真的。您看到它在建立连接之前退出的原因是因为 IO 服务耗尽了工作。由于您让服务耗尽了工作,循环的意义为零,除非您 实际上使用
restart()
:
后续调用
、run()
、run_one()
或poll()
将立即返回,除非之前调用过poll_one()
restart()
因此,真正的解决方法是保持循环原样,但在开始之前先进行工作,例如改变
std::thread serverThread(server_thread_func, std::ref(ioc));
do_accept_connection(m_acceptor, ioc);
至
do_accept_connection(m_acceptor, ioc);
std::thread serverThread(server_thread_func, std::ref(ioc));
全局连接变量是一个问题,尤其是当您接受超过 1 个连接时。通过不使用全局来修复它,例如:
void on_accept_connection(std::error_code ec, std::shared_ptr<tcp::socket> s) {
if (!ec)
std::cout << "SERVER ACCEPTED CONNECTION from " << s->remote_endpoint() << std::endl;
else std::cout << "Reader connection error " << ec << " (" << ec.message() << ")" << std::endl;
}
void do_accept_connection(tcp::acceptor& acceptor, asio::io_context& service) {
auto s = std::make_shared<tcp::socket>(service);
acceptor.async_accept(*s, bind(on_accept_connection, _1, s));
}
注意我们如何将共享指针绑定到完成处理程序中,以使其保持活动状态。
这已经有效:https://coliru.stacked-crooked.com/a/df135c6936c5e37e
Connecting socket...
Connected socket
SERVER ACCEPTED CONNECTION from 127.0.0.1:45958
[eventloop] exit
Closing socket
但是,您应该采用移动套接字的重载,使用内置线程池,不使用第二个 io 服务,使用
io_context
而不是已弃用的 io_service
以及其他内容,例如不使用不必要的原始指针,boost::bind
(或完全绑定)和boost::thread
,传递执行上下文而不是执行器等:Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::asio::ip::tcp;
void on_accept_connection(std::error_code ec, tcp::socket s) {
if (!ec)
std::cout << "Accepted from " << s.remote_endpoint() << std::endl;
else
std::cout << std::endl;
}
void accept_loop(tcp::acceptor& acc) {
acc.async_accept(make_strand(acc.get_executor()), on_accept_connection);
}
int main() {
tcp::endpoint ep{{}, 1900};
using std::this_thread::sleep_for;
asio::thread_pool ioc(1);
tcp::acceptor listener{ioc, ep};
accept_loop(listener);
sleep_for(500ms);
{
// start client:
tcp::socket socket(ioc);
std::cout << "Connecting socket..." << std::endl;
socket.connect(ep);
std::cout << "Connected socket" << std::endl;
sleep_for(500ms);
std::cout << "Closing socket" << std::endl;
}
ioc.join();
}
印刷
Connecting socket...
Connected socket
Accepted from 127.0.0.1:47014
Closing socket
要使其完整,请实际制作
accept_loop
循环:
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;
using boost::asio::ip::tcp;
struct session : std::enable_shared_from_this<session> {
session(tcp::socket s) : s_(std::move(s)) {}
void run() {
std::cout << "Session from " << s_.remote_endpoint() << std::endl;
async_write(s_,
asio::buffer(message_), //
consign(asio::detached, shared_from_this()));
}
private:
tcp::socket s_;
std::string message_ = "Hello from server\n";
};
struct listener {
listener(tcp::endpoint ep) : acc_(ioc_, ep) { accept_loop(); }
private:
asio::thread_pool ioc_{1};
tcp::acceptor acc_;
void accept_loop() {
acc_.async_accept(make_strand(acc_.get_executor()), [this](error_code ec, tcp::socket s) {
if (!ec) {
std::make_shared<session>(std::move(s))->run();
accept_loop();
} else
std::cout << std::endl;
});
}
};
int main() {
tcp::endpoint ep{{}, 1900};
listener server(ep);
std::this_thread::sleep_for(50ms);
for (auto i = 0; i < 10; ++i) {
tcp::iostream is(ep);
std::cout << "Connected: " << is.rdbuf() << std::endl;
}
}
打印例如
Connected: Session from 127.0.0.1:48586
Hello from server
Connected: Session from 127.0.0.1:48588
Hello from server
Connected: Session from 127.0.0.1:48590
Hello from server
Connected: Session from 127.0.0.1:48592
Hello from server
Connected: Session from 127.0.0.1:48594
Hello from server
Connected: Session from 127.0.0.1:48596
Hello from server
Connected: Session from 127.0.0.1:48598
Hello from server
Connected: Session from 127.0.0.1:48600
Hello from server
Connected: Session from 127.0.0.1:48602
Hello from server
Connected: Session from 127.0.0.1:48604
Hello from server