I am creating an udp client pool. The servers will be some other applications running in different computers, and they are suppoused to be alive from beginning. Using a configurable file (not important to problem in example so not included) one to several clients are created so they connect to those servers (1 to 1 relation) in a bidirectional way, sending and receiving.
Sending can be sync because it uses small messages and blocking there in not a problem, but receiving must be async, because answerback can arrive much later after sending.
In my test with only one sockect, it is able to send, but it is not receiving anything at all.
Q1: Where is the problem and how to fix it?
Q2: I also wonder if the use of iterators from std::vector
in the async calls can be problematic at the time new connections are pushed into vector due to its rearangment in memory. This may be a problem?
Q3: I really does not understand why in all examples sender and receiver endpoints (endpoint1 and endpoint2 in example struct Socket
) are different, couldn't they be the same?
My code is next:
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
class Pool
{
struct Socket {
std::string id;
udp::socket socket;
udp::endpoint endpoint1;
udp::endpoint endpoint2;
enum { max_length = 1024 };
std::array<char, max_length> data;
};
public:
void create(const std::string& id, const std::string& host, const std::string& port)
{
udp::resolver resolver(io_context);
sockets.emplace_back(Socket{ id, udp::socket{io_context, udp::v4()}, *resolver.resolve(udp::v4(), host, port).begin() });
receive(id);
}
void send(const std::string& id, const std::string& msg)
{
auto it = std::find_if(sockets.begin(), sockets.end(), [&](auto& socket) { return id == socket.id; });
if (it == sockets.end()) return;
it->data = std::array<char, Socket::max_length>{ 'h', 'e', 'l', 'l', 'o' };
auto bytes = it->socket.send_to(boost::asio::buffer(it->data, 5), it->endpoint1);
}
void receive(const std::string& id)
{
auto it = std::find_if(sockets.begin(), sockets.end(), [&](auto& socket) { return id == socket.id; });
if (it == sockets.end()) return;
it->socket.async_receive_from(
boost::asio::buffer(it->data, Socket::max_length),
it->endpoint2,
[this, id](boost::system::error_code error, std::size_t bytes) {
if (!error && bytes)
bool ok = true;//Call to whatever function
receive(id);
}
);
}
void poll()
{
io_context.poll();
}
private:
boost::asio::io_context io_context;
std::vector<Socket> sockets;
};
int main()
{
Pool clients;
clients.create("ID", "localhost", "55000");
while (true) {
clients.poll();
clients.send("ID", "x");
Sleep(5000);
}
}
CodePudding user response:
Q1: Where is the problem and how to fix it?
You don't really bind to any port, and then you have multiple sockets all receiving unbound udp packets. Likely they're simply competing and something gets lost in the confusion.
Q2: can std::vector be problematic
Yes. Use a std::deque
(stable iterator/references as long as you only push/pop at either end). Otherwise, consider a std::list
or other node-based container.
In your case map<id, socket>
seems more intuitive.
Actually, map<endpoint, peer>
would be a lot more intuitive. Or... you could do without the peers entirely.
Q3: I really does not understand why in all examples sender and receiver endpoints (endpoint1 and endpoint2 in example struct Socket) are different, couldn't they be the same?
Yeah, they could be "the same" if you don't care about overwriting the original endpoint you had sent to.
Here's my simplified take. As others have said, it's not possible/useful to have many UDP sockets "listening" on the same endpoint. That is, provided that you even bound to an endpoint.
So my sample uses a single _socket
with local endpoint :8765.
It can connect to many client endpoints - I chose to replace the id string with the endpoint itself for simplicity. Feel free to add a map<string, endpoint>
for some translation.