英文:
How to make working p2p connection using asio and c++?
问题
我尝试学习有关网络的知识,并决定尝试制作一个点对点终端程序,但我无法正确理解如何做到这一点。这段代码的想法是你创建一个除了调用连接之外的类对象,如果另一端已经执行了相同的操作,你就会连接上,否则你需要等待另一台设备尝试调用连接。但它明显不起作用。我做错了什么?
class other
{
public:
other(asio::io_context& _context, asio::error_code _ec, std::string _IP, int _port);
void Connect();
asio::ip::tcp::endpoint endpoint;
asio::ip::tcp::socket socket;
asio::ip::tcp::acceptor acceptor;
bool isConnected();
private:
void AwaitConnection();
asio::io_context& context;
asio::error_code& ec;
bool connected;
};
other::other(asio::io_context& _context, asio::error_code _ec, std::string _IP, int _port) :
context(_context),
ec(_ec),
endpoint(asio::ip::address::from_string(_IP), _port),
socket(_context),
acceptor(_context),
connected(false)
{
}
void other::Connect()
{
std::cout << "Looking for connection..." << std::endl;
socket.connect(endpoint, ec);
if (!ec)
{
std::cout << "Connected!" << std::endl;
connected = true;
}
else
{
std::cout << "Connection not found: " << ec.message() << std::endl;
acceptor.open(endpoint.protocol(), ec);
acceptor.bind(endpoint, ec);
acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true), ec);
if (ec)
std::cout << ec.message() << std::endl;
socket.close();
if (ec)
std::cout << "Binding socket failed: " << ec.message() << std::endl;
std::cout << "Waiting for connection..." << std::endl;
AwaitConnection();
}
}
void other::AwaitConnection()
{
static std::function<void(const asio::error_code&)> acceptHandler =
[this](const asio::error_code& error)
{
if (!error)
{
std::cout << "Connection has been found!" << std::endl;
connected = true;
}
else
{
std::cout << "Connection failed: " << error.message() << std::endl;
//AwaitConnection();
}
};
acceptor.async_accept(socket, acceptHandler);
}
英文:
I'm trying to learn something about networking and I decided to try to make a p2p terminal program, but I can't get the idea of how to do this correctly. The idea behind this code is that you make an object of class other then call connect and if the other end already did the same you get connected, else you wait for the other device to try to call connect. It just plainly does not work. What am I doing wrong?
class other
{
public:
other(asio::io_context& _context, asio::error_code _ec, std::string _IP, int _port);
void Connect();
asio::ip::tcp::endpoint endpoint;
asio::ip::tcp::socket socket;
asio::ip::tcp::acceptor acceptor;
bool isConnected();
private:
void AwaitConnection();
asio::io_context& context;
asio::error_code& ec;
bool connected;
};
other::other(asio::io_context& _context, asio::error_code _ec, std::string _IP, int _port) :
context(_context),
ec(_ec),
endpoint(asio::ip::address::from_string(_IP), _port),
socket(_context),
acceptor(_context),
connected(false)
{
}
void other::Connect()
{
std::cout << "Looking for connection..." << std::endl;
socket.connect(endpoint, ec);
if (!ec)
{
std::cout << "Connected!" << std::endl;
connected = true;
}
else
{
std::cout << "Connection not found: " << ec.message() << std::endl;
acceptor.open(endpoint.protocol(), ec);
acceptor.bind(endpoint, ec);
acceptor.set_option(asio::ip::tcp::acceptor::reuse_address(true), ec);
if (ec)
std::cout << ec.message() << std::endl;
socket.close();
if (ec)
std::cout << "Binding socket failed: " << ec.message() << std::endl;
std::cout << "Waiting for connection..." << std::endl;
AwaitConnection();
}
}
void other::AwaitConnection()
{
static std::function<void(const asio::error_code&)> acceptHandler =
[this](const asio::error_code& error)
{
if (!error)
{
std::cout << "Connection has been found!" << std::endl;
connected = true;
}
else
{
std::cout << "Connection failed: " << error.message() << std::endl;
//AwaitConnection();
}
};
acceptor.async_accept(socket, acceptHandler);
}```
</details>
# 答案1
**得分**: 1
Here is the translation of the provided content:
如果你启用了编译器警告,编译器将告诉你一些你做错的事情(比如将`ec`引用绑定到栈局部变量):
, ec(_ec)
这只是[UB](https://en.wikipedia.org/wiki/Undefined_behavior)(未定义行为)。
实际上,你并没有展示代码如何实际使用这个,但是看起来你想在错误处理方面进行集中处理。那我建议使用异常。
从高层来看,TCP 本质上是客户端/服务器模型。一方监听,另一方连接。当然,你可以通过使双方都能够扮演两种角色来隐藏这一点,但事实仍然是,一方是服务器(监听、接受连接),另一方是客户端(连接)。
最好在代码中明确这一区分。否则,你很容易陷入这样的情况,即两端都在监听(例如,因为它们同时启动,都尝试连接到“其他”正在监听的实例,但失败了,所以它们都开始监听传入的连接)。
如果你想使这个“自动化”,你可能只需始终启动一个监听器,并将无法监听视为非致命错误(例如,因为已在同一台机器上运行了“其他”正在监听的实例)。
明确区分的一个关键原因是因为你现在使用“主机名”创建了混淆。你同时使用相同的`“_IP”`值进行`connect`或`listen`。这很少有意义,除非你已经提前知道将采取哪种角色。那么,为什么混淆这两种角色呢?
最后,你没有展示代码在何处运行`io_context`。由于你似乎不需要异步 IO,你可以简化。
以下代码可以工作:
```cpp
class Object {
public:
void Connect(std::string const& ip_address, uint16_t port) {
std::cout << "Looking for connection..." << std::endl;
socket_.connect({asio::ip::address::from_string(ip_address), port});
std::cout << "Connected!" << std::endl;
connected_ = true;
}
void Listen(uint16_t port) {
acceptor_.open(tcp::v4());
acceptor_.bind(tcp::endpoint{{}, port});
acceptor_.set_option(tcp::acceptor::reuse_address(true));
acceptor_.listen();
socket_ = acceptor_.accept();
std::cout << "Connection has been found!" << std::endl;
connected_ = true;
}
bool isConnected() const { return connected_; }
private:
tcp::socket socket_{asio::system_executor{}};
tcp::acceptor acceptor_{asio::system_executor{}};
bool connected_ = false;
};
添加最小的代码以实际发送/接收一些消息:
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using tcp = asio::ip::tcp;
class Object {
public:
void Connect(std::string const& ip_address, uint16_t port) {
std::cout << "Looking for connection..." << std::endl;
socket_.connect({asio::ip::address::from_string(ip_address), port});
std::cout << "Connected!" << std::endl;
connected_ = true;
}
void Listen(uint16_t port) {
acceptor_.open(tcp::v4());
acceptor_.set_option(tcp::acceptor::reuse_address(true));
acceptor_.bind(tcp::endpoint{{}, port});
acceptor_.listen();
socket_ = acceptor_.accept();
std::cout << "Connection has been found!" << std::endl;
connected_ = true;
}
void Send(std::string const& msg) {
assert(isConnected());
asio::write(socket_, asio::buffer(msg + "\n"));
}
std::string Receive() {
assert(isConnected());
asio::read_until(socket_, buf_, "\n");
std::string msg;
getline(std::istream(&buf_), msg);
return msg;
}
bool isConnected() const { return connected_; }
private:
tcp::socket socket_{asio::system_executor{}};
tcp::acceptor acceptor_{asio::system_executor{}};
bool connected_ = false;
asio::streambuf buf_;
};
int main(int argc, char**) try {
if (argc > 1) { // 服务器
Object one;
one.Listen(7878);
std::cout << "Received message " << quoted(one.Receive()) << std::endl;
one.Send("Response from server");
} else { // 客户端
Object two;
two.Connect("127.0.0.1", 7878);
two.Send("Hello from client");
std::cout << "Peer responded with " << quoted(two.Receive()) << std::endl;
}
} catch (boost::system::system_error const& se) {
std::cout << "Failed: " << se.code().message() << std::endl;
}
演示:
英文:
If you enable compiler warnings, the compiler will tell you some of the things you are doing wrong (like binding ec
reference to stack-local):
, ec(_ec)
That's just UB.
You don't actually show the code actually using this, but it seems that you want to do the error-handling centrally. I'd use exceptions then.
On the high level, TCP is client/server by nature. One side listens, the other one connects. Of course you can hide this by making both sides capable of acting in both roles, but the fact remains, one side is the server (listening, accepting) the other is the client (connecting).
It's probably best if you make the distinction in your code. Otherwise you will easily end up in the situation where both ends are listening (e.g. because they start at the same time, and both tried to connect to an "other" instance listening, but failed, so they both start listening for incoming connections instead.
If you want to make that "automatic" you might just always start a listener, and treat failure to listen as non-fatal (e.g. because the "other" instance that was already listening is running on the same machine).
One key reason to make the distinction explicit is because you're now creating confusion with "hostnames". You use the same "_IP"
value both to connect
or to listen
to. That will rarely make sense, unless you do already know ahead of time which role will be taken. So, why conflate the two roles?
Finally, you don't show that/where you are running the io_context
. Since you don't seem to require asynchronous IO, you might simplify.
This would work:
class Object {
public:
void Connect(std::string const& ip_address, uint16_t port) {
std::cout << "Looking for connection..." << std::endl;
socket_.connect({asio::ip::address::from_string(ip_address), port});
std::cout << "Connected!" << std::endl;
connected_ = true;
}
void Listen(uint16_t port) {
acceptor_.open(tcp::v4());
acceptor_.bind(tcp::endpoint{{}, port});
acceptor_.set_option(tcp::acceptor::reuse_address(true));
acceptor_.listen();
socket_ = acceptor_.accept();
std::cout << "Connection has been found!" << std::endl;
connected_ = true;
}
bool isConnected() const { return connected_; }
private:
tcp::socket socket_{asio::system_executor{}};
tcp::acceptor acceptor_{asio::system_executor{}};
bool connected_ = false;
};
Adding minimal code to actually send/receive some messages:
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using tcp = asio::ip::tcp;
class Object {
public:
void Connect(std::string const& ip_address, uint16_t port) {
std::cout << "Looking for connection..." << std::endl;
socket_.connect({asio::ip::address::from_string(ip_address), port});
std::cout << "Connected!" << std::endl;
connected_ = true;
}
void Listen(uint16_t port) {
acceptor_.open(tcp::v4());
acceptor_.set_option(tcp::acceptor::reuse_address(true));
acceptor_.bind(tcp::endpoint{{}, port});
acceptor_.listen();
socket_ = acceptor_.accept();
std::cout << "Connection has been found!" << std::endl;
connected_ = true;
}
void Send(std::string const& msg) {
assert(isConnected());
asio::write(socket_, asio::buffer(msg + "\n"));
}
std::string Receive() {
assert(isConnected());
asio::read_until(socket_, buf_, "\n");
std::string msg;
getline(std::istream(&buf_), msg);
return msg;
}
bool isConnected() const { return connected_; }
private:
tcp::socket socket_{asio::system_executor{}};
tcp::acceptor acceptor_{asio::system_executor{}};
bool connected_ = false;
asio::streambuf buf_;
};
int main(int argc, char**) try {
if (argc > 1) { // server
Object one;
one.Listen(7878);
std::cout << "Received message " << quoted(one.Receive()) << std::endl;
one.Send("Response from server");
} else { // client
Object two;
two.Connect("127.0.0.1", 7878);
two.Send("Hello from client");
std::cout << "Peer responded with " << quoted(two.Receive()) << std::endl;
}
} catch (boost::system::system_error const& se) {
std::cout << "Failed: " << se.code().message() << std::endl;
}
Demo:
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论