2

I'm using asio library (C++) and I would like to create a pair of server/client such that:

  1. The server is streaming data on some port;
  2. The client connect to that port and receive such data.

Now, in usual client/server applications the server is "listening" to some port, while the client connects to this port. In what I described above it seems that the part are switched, so I manage to write the following code for the server:

#include <iostream>
#include <numeric>
#include <vector>

#include "asio.hpp"

int main() {
  constexpr int size = 10;

  std::vector<int> vct(size);
  std::iota(vct.begin(), vct.end(), 0);

  asio::io_context context;
  asio::ip::udp::resolver resolver{context};
  asio::ip::udp::socket socket{context};

  socket.open(asio::ip::udp::v4());
  socket.connect(asio::ip::udp::endpoint(asio::ip::udp::v4(), 10888));

  size_t len = socket.send(asio::buffer(vct));

  std::cout << "Sent " << len << " b." << std::endl;

  return EXIT_SUCCESS;
}

and the following for the client:

#include <iostream>
#include <vector>

#include "asio.hpp"


int main()
{
    constexpr int size = 10;
    std::vector<int> vct(size);
    
    asio::io_context context;
    asio::ip::udp::endpoint local_endpoint{asio::ip::address::from_string("127.0.0.1"), 10888};
    asio::ip::udp::endpoint sender_endpoint;
    asio::ip::udp::socket socket{context};

    socket.open(asio::ip::udp::v4());
    socket.bind(local_endpoint);
    size_t len = socket.receive_from(asio::buffer(vct), sender_endpoint);

    std::cout << "Received " << len << " b from " << sender_endpoint << std::endl;

    for (auto i : vct)
        std::cout << i << std::endl;

    return EXIT_SUCCESS;
}

So I used connect in the server while I use bind in the client. This makes me a bit upset even if it seems to work. My questions are

  1. Is this the correct way to setup a "streaming server" and a receiver (let us drop for the moment the issue about async, I mean here at the level of connection)
  2. Should bind and connect be used this way?
genpfault
  • 51,148
  • 11
  • 85
  • 139
MaPo
  • 613
  • 4
  • 9
  • Multiple clients? – Mikel F May 15 '23 at 21:09
  • 2
    Also you may be a bit hung up on the label of 'server' and 'client'. – Mikel F May 15 '23 at 21:10
  • The point is that I really do not care whom I'm sending to. And I do now know who will connect to the "streaming port". Is this an instance of broadcast? – MaPo May 15 '23 at 21:10
  • @MikelF. I agree with you. Sender/receiver would be more appropriate. – MaPo May 15 '23 at 21:12
  • Is the sender going to push out data even if there are no receivers? – Mikel F May 15 '23 at 21:54
  • 1
    Yes it is. What confuses me is that two ports seem to be necessary: one for the local endpoint (the one used in bind) and the other for the remote endpoint, even if it is the broadcast address (the one used in connect). How can I clarify this? – MaPo May 15 '23 at 21:59
  • @MaPo you need a combination of correct socket options and actual broadcast address. I just posted a working example of how I understand it. – sehe May 15 '23 at 22:08
  • I'd strongly recommend using multicast for this instead of broadcast. – Mikel F May 15 '23 at 22:24
  • "Now, in usual client/server applications the server is "listening" to some port, while the client connects to this port. In what I described above it seems that the part are switched" - Why do you say that they are switched? The client still connects to some well known port. The server is streaming data from a well known port. Seems straight forward to me. – Jesper Juhl May 16 '23 at 01:14

1 Answers1

2

It's not exactly clear what "makes you a little upset".

Do you mean you want to have the roles reversed? It does make more conventional sense for client to connect to a wellknown sender.

I agree that's a broadcast situation. There are surprisingly few examples of it around on the web. I suspect it can get more complicated, but below will be a simple example that makes your program work.

Simplify

First, I've combined your sender/receiver into the same source:

Live On Coliru

#include <boost/asio.hpp>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#include <numeric>
namespace asio = boost::asio;
using asio::ip::udp;
template <> struct fmt::formatter<udp::endpoint> : ostream_formatter {};

int main(int argc, char**) {
    constexpr int    size = 10;
    std::vector<int> vct(size);

    asio::io_context context;
    udp::socket      socket{context};
    socket.open(udp::v4());

    if (argc > 1) { // sender
        socket.connect({{}, 10888});

        for (int i : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
            iota(begin(vct), end(vct), size * i);
            size_t len = socket.send(asio::buffer(vct));
            fmt::print("Sent #{}, {}b, vct {}\n", i, len, vct);
        }
        socket.send(asio::buffer(vct, 0));
    } else { // receiver
        socket.bind({{}, 10888});
        udp::endpoint sender_endpoint;

        while (size_t len = socket.receive_from(asio::buffer(vct), sender_endpoint))
            fmt::print("Received {}b from {}, vct {}\n", len, sender_endpoint, vct);
    }
    fmt::print("Bye\n");
}

The output is a bit jumbled there, so here it is on my box for clarity:

enter image description here

Broadcast: Swap sender/receiver

You can have your cake and eat it too. We make sure to send to a broadcast address, and bind the client to that:

Live On Coliru

#include <boost/asio.hpp>
#include <fmt/ostream.h>
#include <fmt/ranges.h>
#include <numeric>
namespace asio = boost::asio;
using asio::ip::udp;
template <> struct fmt::formatter<udp::endpoint> : ostream_formatter {};

int main(int argc, char**) {
    constexpr int    size = 10;
    std::vector<int> vct(size);

    asio::io_context context;
    udp::socket      socket{context};
    socket.open(udp::v4());

    udp::endpoint ep{asio::ip::address_v4::broadcast(), 10888};

    if (argc > 1) { // sender
        socket.set_option(udp::socket::reuse_address(true));
        socket.set_option(udp::socket::broadcast(true));

        for (int i : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) {
            iota(begin(vct), end(vct), size * i);
            auto len = socket.send_to(asio::buffer(vct), ep);
            fmt::print("Sent #{}, {}b, vct {}\n", i, len, vct);
        }
        socket.send_to(asio::buffer(vct, 0), ep);
    } else { // receiver
        socket.bind(ep);

        udp::endpoint sender_endpoint;
        while (size_t len = socket.receive_from(asio::buffer(vct), sender_endpoint))
            fmt::print("Received {}b from {}, vct {}\n", len, sender_endpoint, vct);
    }
    fmt::print("Bye\n");
}

Again, live:

enter image description here

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thank you for your answer. What does it mean for the receiver to be bound to the broadcast address? – MaPo May 16 '23 at 19:42
  • It means it... binds to that address. (The code is included) It allows the broadcasts on that address to be received, it appears (the output is included too) – sehe May 16 '23 at 20:00
  • I thought that a something sent to broadcasts address was available to all IPs. But in the example above seems that the receiver needs to be bound to that address; therefore I do not see anything special in it. Couldn't it be any other address? Could multiple client be bound on that address? – MaPo May 16 '23 at 20:15
  • Have you tried? Did you see the `reuse_address` option? – sehe May 16 '23 at 21:24