我正在尝试使用 boost::socket
编写一个非常简单的客户端/服务器应用程序。我需要一个服务器来运行,一个客户端来连接、发送数据、断开连接,并可能稍后重新连接并重复。
减少到最少的代码在这里:
服务器应用程序:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
class TheServer
{
public:
TheServer(int port) : m_port(port)
{
m_pIOService = new boost::asio::io_service;
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
listenForNewConnection();
}
~TheServer()
{
m_bContinueReading = false;
m_pIOService->stop();
m_pThread->join();
delete m_pThread;
delete m_pSocket;
delete m_pAcceptor;
delete m_pIOService;
}
void listenForNewConnection()
{
if (m_pSocket)
delete m_pSocket;
if (m_pAcceptor)
delete m_pAcceptor;
// start new acceptor operation
m_pSocket = new tcp::socket(*m_pIOService);
m_pAcceptor = new tcp::acceptor(*m_pIOService, tcp::endpoint(tcp::v4(), m_port));
std::cout << "Starting async_accept" << std::endl;
m_pAcceptor->async_accept(*m_pSocket,
boost::bind<void>(&TheServer::readSession, this, boost::asio::placeholders::error));
}
void readSession(boost::system::error_code error)
{
if (!error)
{
std::cout << "Connection established" << std::endl;
while ( m_bContinueReading )
{
static unsigned char buffer[1000];
boost::system::error_code error;
size_t length = m_pSocket->read_some(boost::asio::buffer(&buffer, 1000), error);
if (!error && length != 0)
{
std::cout << "Received " << buffer << std::endl;
}
else
{
std::cout << "Received error, connection likely closed by peer" << std::endl;
break;
}
}
std::cout << "Connection closed" << std::endl;
listenForNewConnection();
}
else
{
std::cout << "Connection error" << std::endl;
}
std::cout << "Ending readSession" << std::endl;
}
void run()
{
while (m_bContinueReading)
m_pIOService->run_one();
std::cout << "Exiting run thread" << std::endl;
}
bool m_bContinueReading = true;
boost::asio::io_service* m_pIOService = NULL;
tcp::socket* m_pSocket = NULL;
tcp::acceptor* m_pAcceptor = NULL;
boost::thread* m_pThread = NULL;
int m_port;
};
int main(int argc, char* argv[])
{
TheServer* server = new TheServer(1900);
std::cout << "Press Enter to quit" << std::endl;
std::string sGot;
getline(std::cin, sGot);
delete server;
return 0;
}
客户端应用程序:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
int main(int argc, char* argv[])
{
std::cout << std::endl;
std::cout << "Starting client" << std::endl;
using boost::asio::ip::tcp;
boost::asio::io_service* m_pIOService = NULL;
tcp::socket* m_pSocket = NULL;
try
{
m_pIOService = new boost::asio::io_service;
std::stringstream sPort;
sPort << 1900;
tcp::resolver resolver(*m_pIOService);
tcp::resolver::query query(tcp::v4(), "localhost", sPort.str());
tcp::resolver::iterator iterator = resolver.resolve(query);
m_pSocket = new tcp::socket(*m_pIOService);
m_pSocket->connect(*iterator);
std::cout << "Client conected" << std::endl;
std::string hello = "Hello World";
boost::asio::write( *m_pSocket, boost::asio::buffer(hello.data(), hello.size()) );
boost::this_thread::sleep(boost::posix_time::milliseconds(100));
hello += "(2)";
boost::asio::write(*m_pSocket, boost::asio::buffer(hello.data(), hello.size()));
}
catch (std::exception& e)
{
delete m_pSocket;
m_pSocket = NULL;
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
请注意,我使用非阻塞 async_accept 以便在按下 Enter 时能够干净地停止服务器。
在Windows下,它工作得很好,我运行服务器,它输出:
Starting async_accept
Press Enter to quit
对于每个客户端应用程序运行,它会输出:
Starting client
Client conected
和服务器应用程序输出:
Connection established
Received Hello World
Received Hello World(2)
Received error, connection likely closed by peer
Connection closed
Starting async_accept
Ending readSession
然后,当我在服务器应用程序控制台中按 Enter 时,它会输出 Exiting run thread
并干净地停止。
现在,当我在 Linux 下编译相同的代码时,客户端输出与 Windows 下相同,但服务器端没有任何反应...
知道出了什么问题吗?
最佳答案
有很多可疑的元素。
m_bContinueReading
上有一场经典的数据竞赛。 。您从另一个线程写入,但由于数据竞争,另一个线程可能永远不会看到更改。第二个竞争条件可能是您的问题:
m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this)); listenForNewConnection();
这里是
run
线程可能会在您发布第一篇作品之前完成。您可以使用工作防护装置来防止这种情况发生。在您的特定代码中,您已经通过重新排序行来修复它:listenForNewConnection(); m_pThread = new boost::thread(boost::bind<void>(&TheServer::run, this));
I would not do this, because I would not have those statements in my constructor body. See below for the work guard solution
存在大量原始指针处理和新建/删除操作,这只会引发错误。
您使用
buffer
假设它以 NUL 结尾。这是特别没有根据的,因为您使用read_some
当消息到达网络时,它将读取部分消息。您使用
static
buffer 而代码可能有不同的类实例。这是非常错误的优化。相反,阻止所有分配!结合上一条:char buffer[1000]; while (m_bContinueReading) { size_t length = m_Socket.read_some(asio::buffer(&buffer, 1000), ec); std::cout << "Received " << length << " (" << quoted(std::string(buffer, length)) << "), " << ec.message() << std::endl; if (ec.failed()) break; }
您总是在不需要的情况下启动一个新的接受器:单个接受器可以接受任意数量的连接。事实上,所示的方法遇到了问题
延迟连接可能会阻止新接受器绑定(bind)到同一端口。您还可以通过以下方式缓解这种情况:
m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
被破坏的接受器可能有积压的连接,这些连接将被丢弃
通常您希望支持并发连接,因此您可以拆分“readSession”并立即接受下一个连接。现在,奇怪的是,您的代码似乎期望客户端连接,直到服务器被提示关闭(从控制台)但是之后您以某种方式开始监听新连接(即使您知道该服务将被关闭)停止,
m_bContinueReading
将保留false
)。从长远来看,你不想销毁接受器,除非有什么东西使它无效。实际上,这种情况很少见(例如,在 Linux 上,接受器将很高兴地在禁用/重新启用网络适配器的情况下幸存下来)。
您有虚假的显式模板参数 (
bind<void>
)。这是一种反模式,可能会导致微妙的问题与缓冲区类似(只需说
asio::buffer(buffer)
,不再有正确性问题。事实上,不要使用 C 样式数组:std::array<char, 1000> buffer; size_t n = m_Socket.read_some(asio::buffer(buffer), ec); std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n)) << " (" << ec.message() << ")" << std::endl;
而不是运行手册
run_one()
循环(其中 forget to handle exceptions ),考虑“只是”让服务run()
。然后你就可以.cancel()
接受者让服务停止工作。事实上,您的代码中不需要这种微妙之处,因为您的代码无论如何已经强制“不正常”关闭:
m_IOService.stop(); // nuclear option m_Thread.join();
例如更温和
m_Acceptor.cancel(); m_Socket.cancel(); m_Thread.join();
在这种情况下,您可以回复完成
error_code == error::operation_aborted
停止 session /接受循环。Technically, you may be able to do away with the boolean flag altogether. I keep it because it allows us to handle multiple session-per-thread in "fire-and-forget" manner.
在客户端中,您会遇到许多相同的问题,而且还有一个问题 您只查看第一个解析器结果(假设有一个), 忽略其余的。您可以使用
asio::connect
代替m_Socket.connect
尝试所有已解决的条目
解决大部分问题,简化代码:
<强> Live On Coliru
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <boost/optional.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using namespace std::chrono_literals;
using boost::system::error_code;
class TheServer {
public:
TheServer(int port) : m_port(port) {
m_Acceptor.set_option(tcp::acceptor::reuse_address(true));
do_accept();
}
~TheServer() {
m_shutdownRequested = true;
m_Work.reset(); // release the work-guard
m_Acceptor.cancel();
m_Thread.join();
}
private:
void do_accept() {
std::cout << "Starting async_accept" << std::endl;
m_Acceptor.async_accept( //
m_Socket, boost::bind(&TheServer::on_accept, this, asio::placeholders::error));
}
void on_accept(error_code ec) {
if (!ec) {
std::cout << "Connection established " << m_Socket.remote_endpoint() << std::endl;
// leave session running in the background:
std::thread(&TheServer::read_session_thread, this, std::move(m_Socket)).detach();
do_accept(); // and immediately accept new connection(s)
} else {
std::cout << "Connection error (" << ec.message() << ")" << std::endl;
std::cout << "Ending readSession" << std::endl;
}
}
void read_session_thread(tcp::socket sock) {
std::array<char, 1000> buffer;
for (error_code ec;;) {
size_t n = sock.read_some(asio::buffer(buffer), ec);
std::cout << "Received " << n << " " << quoted(std::string(buffer.data(), n)) << " ("
<< ec.message() << ")" << std::endl;
if (ec.failed() || m_shutdownRequested)
break;
}
std::cout << "Connection closed" << std::endl;
}
void thread_func() {
// 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
for (;;) {
try {
m_IOService.run();
break; // exited normally
} catch (std::exception const& e) {
std::cerr << "[eventloop] error: " << e.what();
} catch (...) {
std::cerr << "[eventloop] unexpected error";
}
}
std::cout << "Exiting service thread" << std::endl;
}
std::atomic_bool m_shutdownRequested{false};
uint16_t m_port;
asio::io_service m_IOService;
boost::optional<asio::io_service::work> m_Work{m_IOService};
tcp::socket m_Socket{m_IOService};
tcp::acceptor m_Acceptor{m_IOService, tcp::endpoint{tcp::v4(), m_port}};
std::thread m_Thread{boost::bind(&TheServer::thread_func, this)};
};
constexpr uint16_t s_port = 1900;
void run_server() {
TheServer server(s_port);
std::cout << "Press Enter to quit" << std::endl;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
void run_client() {
std::cout << std::endl;
std::cout << "Starting client" << std::endl;
using asio::ip::tcp;
try {
asio::io_service m_IOService;
tcp::resolver resolver(m_IOService);
auto iterator = resolver.resolve("localhost", std::to_string(s_port));
tcp::socket m_Socket(m_IOService);
connect(m_Socket, iterator);
std::cout << "Client connected" << std::endl;
std::string hello = "Hello World";
write(m_Socket, asio::buffer(hello));
std::this_thread::sleep_for(100ms);
hello += "(2)";
write(m_Socket, asio::buffer(hello));
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
}
int main(int argc, char**) {
if (argc>1)
run_server();
else
run_client();
}
关于c++ - 使用 C++/boost 套接字的简单客户端/服务器在 Windows 下工作,但在 Linux 下失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75211089/