A site for solving at least some of your technical problems...
A site for solving at least some of your technical problems...

I have noticed that quite a few people were trying to create UDP communication and I thought that proposing my class could help them. This is very basic as it does not define anything such as the size of a packet or any protocol to ensure arrival of the packets. However, it can be useful if you want to send a signal from one process to another, which is exactly how I use this implementation (i.e. I send a PING message to wake up a background process whenever the front end adds data to the database.)
This code is part of the Snap! C++ implementation. The latest version can be found in the GitHub Snap C++ repository. Look under the eventdispatcher directory and search for UDP. The latest update, in 2018, includes proper broadcast and multicast support. i.e. attaching the socket to a group. The newest update also requires the use of the libaddr (also a Snap! C++ project) library to handle the address and port and when using TCP the SNI hostname.
UDP can be used in several different ways:

Header file:
// UDP Client Server -- send/receive UDP packets
// Copyright (C) 2013 Made to Order Software Corp.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#ifndef SNAP_UDP_CLIENT_SERVER_H
#define SNAP_UDP_CLIENT_SERVER_H
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdexcept>
namespace udp_client_server
{
class udp_client_server_runtime_error : public std::runtime_error
{
public:
udp_client_server_runtime_error(const char *w) : std::runtime_error(w) {}
};
class udp_client
{
public:
udp_client(const std::string& addr, int port);
~udp_client();
int get_socket() const;
int get_port() const;
std::string get_addr() const;
int send(const char *msg, size_t size);
private:
int f_socket;
int f_port;
std::string f_addr;
struct addrinfo * f_addrinfo;
};
class udp_server
{
public:
udp_server(const std::string& addr, int port);
~udp_server();
int get_socket() const;
int get_port() const;
std::string get_addr() const;
int recv(char *msg, size_t max_size);
int timed_recv(char *msg, size_t max_size, int max_wait_ms);
private:
int f_socket;
int f_port;
std::string f_addr;
struct addrinfo * f_addrinfo;
};
} // namespace udp_client_server
#endif
// SNAP_UDP_CLIENT_SERVER_H
// vim: ts=4 sw=4 et
C++ Implementation:
// UDP Client & Server -- classes to ease handling sockets
// Copyright (C) 2013 Made to Order Software Corp.
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
#include "udp_client_server.h"
#include <string.h>
#include <unistd.h>
namespace udp_client_server
{
// ========================= CLIENT =========================
/** \brief Initialize a UDP client object.
*
* This function initializes the UDP client object using the address and the
* port as specified.
*
* The port is expected to be a host side port number (i.e. 59200).
*
* The \p addr parameter is a textual address. It may be an IPv4 or IPv6
* address and it can represent a host name or an address defined with
* just numbers. If the address cannot be resolved then an error occurs
* and constructor throws.
*
* \note
* The socket is open in this process. If you fork() or exec() then the
* socket will be closed by the operating system.
*
* \warning
* We only make use of the first address found by getaddrinfo(). All
* the other addresses are ignored.
*
* \exception udp_client_server_runtime_error
* The server could not be initialized properly. Either the address cannot be
* resolved, the port is incompatible or not available, or the socket could
* not be created.
*
* \param[in] addr The address to convert to a numeric IP.
* \param[in] port The port number.
*/
udp_client::udp_client(const std::string& addr, int port)
: f_port(port)
, f_addr(addr)
{
char decimal_port[16];
snprintf(decimal_port, sizeof(decimal_port), "%d", f_port);
decimal_port[sizeof(decimal_port) / sizeof(decimal_port[0]) - 1] = '\0';
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
int r(getaddrinfo(addr.c_str(), decimal_port, &hints, &f_addrinfo));
if(r != 0 || f_addrinfo == NULL)
{
throw udp_client_server_runtime_error(("invalid address or port: \"" + addr + ":" + decimal_port + "\"").c_str());
}
f_socket = socket(f_addrinfo->ai_family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if(f_socket == -1)
{
freeaddrinfo(f_addrinfo);
throw udp_client_server_runtime_error(("could not create socket for: \"" + addr + ":" + decimal_port + "\"").c_str());
}
}
/** \brief Clean up the UDP client object.
*
* This function frees the address information structure and close the socket
* before returning.
*/
udp_client::~udp_client()
{
freeaddrinfo(f_addrinfo);
close(f_socket);
}
/** \brief Retrieve a copy of the socket identifier.
*
* This function return the socket identifier as returned by the socket()
* function. This can be used to change some flags.
*
* \return The socket used by this UDP client.
*/
int udp_client::get_socket() const
{
return f_socket;
}
/** \brief Retrieve the port used by this UDP client.
*
* This function returns the port used by this UDP client. The port is
* defined as an integer, host side.
*
* \return The port as expected in a host integer.
*/
int udp_client::get_port() const
{
return f_port;
}
/** \brief Retrieve a copy of the address.
*
* This function returns a copy of the address as it was specified in the
* constructor. This does not return a canonalized version of the address.
*
* The address cannot be modified. If you need to send data on a different
* address, create a new UDP client.
*
* \return A string with a copy of the constructor input address.
*/
std::string udp_client::get_addr() const
{
return f_addr;
}
/** \brief Send a message through this UDP client.
*
* This function sends \p msg through the UDP client socket. The function
* cannot be used to change the destination as it was defined when creating
* the udp_client object.
*
* The size must be small enough for the message to fit. In most cases we
* use these in Snap! to send very small signals (i.e. 4 bytes commands.)
* Any data we would want to share remains in the Cassandra database so
* that way we can avoid losing it because of a UDP message.
*
* \param[in] msg The message to send.
* \param[in] size The number of bytes representing this message.
*
* \return -1 if an error occurs, otherwise the number of bytes sent. errno
* is set accordingly on error.
*/
int udp_client::send(const char *msg, size_t size)
{
return sendto(f_socket, msg, size, 0, f_addrinfo->ai_addr, f_addrinfo->ai_addrlen);
}
// ========================= SEVER =========================
/** \brief Initialize a UDP server object.
*
* This function initializes a UDP server object making it ready to
* receive messages.
*
* The server address and port are specified in the constructor so
* if you need to receive messages from several different addresses
* and/or port, you'll have to create a server for each.
*
* The address is a string and it can represent an IPv4 or IPv6
* address.
*
* Note that this function calls connect() to connect the socket
* to the specified address. To accept data on different UDP addresses
* and ports, multiple UDP servers must be created.
*
* \note
* The socket is open in this process. If you fork() or exec() then the
* socket will be closed by the operating system.
*
* \warning
* We only make use of the first address found by getaddrinfo(). All
* the other addresses are ignored.
*
* \exception udp_client_server_runtime_error
* The udp_client_server_runtime_error exception is raised when the address
* and port combinaison cannot be resolved or if the socket cannot be
* opened.
*
* \param[in] addr The address we receive on.
* \param[in] port The port we receive from.
*/
udp_server::udp_server(const std::string& addr, int port)
: f_port(port)
, f_addr(addr)
{
char decimal_port[16];
snprintf(decimal_port, sizeof(decimal_port), "%d", f_port);
decimal_port[sizeof(decimal_port) / sizeof(decimal_port[0]) - 1] = '\0';
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
int r(getaddrinfo(addr.c_str(), decimal_port, &hints, &f_addrinfo));
if(r != 0 || f_addrinfo == NULL)
{
throw udp_client_server_runtime_error(("invalid address or port for UDP socket: \"" + addr + ":" + decimal_port + "\"").c_str());
}
f_socket = socket(f_addrinfo->ai_family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
if(f_socket == -1)
{
freeaddrinfo(f_addrinfo);
throw udp_client_server_runtime_error(("could not create UDP socket for: \"" + addr + ":" + decimal_port + "\"").c_str());
}
r = bind(f_socket, f_addrinfo->ai_addr, f_addrinfo->ai_addrlen);
if(r != 0)
{
freeaddrinfo(f_addrinfo);
close(f_socket);
throw udp_client_server_runtime_error(("could not bind UDP socket with: \"" + addr + ":" + decimal_port + "\"").c_str());
}
}
/** \brief Clean up the UDP server.
*
* This function frees the address info structures and close the socket.
*/
udp_server::~udp_server()
{
freeaddrinfo(f_addrinfo);
close(f_socket);
}
/** \brief The socket used by this UDP server.
*
* This function returns the socket identifier. It can be useful if you are
* doing a select() on many sockets.
*
* \return The socket of this UDP server.
*/
int udp_server::get_socket() const
{
return f_socket;
}
/** \brief The port used by this UDP server.
*
* This function returns the port attached to the UDP server. It is a copy
* of the port specified in the constructor.
*
* \return The port of the UDP server.
*/
int udp_server::get_port() const
{
return f_port;
}
/** \brief Return the address of this UDP server.
*
* This function returns a verbatim copy of the address as passed to the
* constructor of the UDP server (i.e. it does not return the canonalized
* version of the address.)
*
* \return The address as passed to the constructor.
*/
std::string udp_server::get_addr() const
{
return f_addr;
}
/** \brief Wait on a message.
*
* This function waits until a message is received on this UDP server.
* There are no means to return from this function except by receiving
* a message. Remember that UDP does not have a connect state so whether
* another process quits does not change the status of this UDP server
* and thus it continues to wait forever.
*
* Note that you may change the type of socket by making it non-blocking
* (use the get_socket() to retrieve the socket identifier) in which
* case this function will not block if no message is available. Instead
* it returns immediately.
*
* \param[in] msg The buffer where the message is saved.
* \param[in] max_size The maximum size the message (i.e. size of the \p msg buffer.)
*
* \return The number of bytes read or -1 if an error occurs.
*/
int udp_server::recv(char *msg, size_t max_size)
{
return ::recv(f_socket, msg, max_size, 0);
}
/** \brief Wait for data to come in.
*
* This function waits for a given amount of time for data to come in. If
* no data comes in after max_wait_ms, the function returns with -1 and
* errno set to EAGAIN.
*
* The socket is expected to be a blocking socket (the default,) although
* it is possible to setup the socket as non-blocking if necessary for
* some other reason.
*
* This function blocks for a maximum amount of time as defined by
* max_wait_ms. It may return sooner with an error or a message.
*
* \param[in] msg The buffer where the message will be saved.
* \param[in] max_size The size of the \p msg buffer in bytes.
* \param[in] max_wait_ms The maximum number of milliseconds to wait for a message.
*
* \return -1 if an error occurs or the function timed out, the number of bytes received otherwise.
*/
int udp_server::timed_recv(char *msg, size_t max_size, int max_wait_ms)
{
fd_set s;
FD_ZERO(&s);
FD_SET(f_socket, &s);
struct timeval timeout;
timeout.tv_sec = max_wait_ms / 1000;
timeout.tv_usec = (max_wait_ms % 1000) * 1000;
int retval = select(f_socket + 1, &s, &s, &s, &timeout);
if(retval == -1)
{
// select() set errno accordingly
return -1;
}
if(retval > 0)
{
// our socket has data
return ::recv(f_socket, msg, max_size, 0);
}
// our socket has no data
errno = EAGAIN;
return -1;
}
} // namespace udp_client_server
// vim: ts=4 sw=4 et
Re: A C++ implementation of a UDP client/server
Now I think I got it!
Re: A C++ implementation of a UDP client/server
There is a comment about that issue. The C
recv()function will do a read with a size which may be larger than the message.What I would do is make the socket non-blocking so that way I can read as many messages as possible until it tells me EAGAIN. At that point I know that the socket buffers are empty and it will return with -1 meaning that nothing was loaded in your
msgbuffer.I'll also update the link to the new location of the most recent version. It's also on github, but now I have a project named eventdispatcher which takes care of all of that under the hood for us.
Re: A C++ implementation of a UDP client/server
I've tried implementing this code, and for the most part it works. But when I use timed_recv instead of recv, it won't time out. It'll get stuck waiting like recv