#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <thread>
#include <signal.h>
#include <cstring>
#include <ctime>
#include <iostream>
const char HELLO_GROUP[] = "225.0.0.37";
const int HELLO_PORT = 12345;
const int MSG_BUF_SIZE = 256;
static volatile bool keepRunning = true;
void usage(const char* progname)
{
printf("Usage:\n %s [<mcast_group> <port>]\n", progname);
printf("Example:\n %s %s %d\n", progname, HELLO_GROUP, HELLO_PORT);
}
void sigIntHandler(int)
{
printf("Got SIGINT (Ctrl+C). Quitting...\n");
keepRunning = false;
}
void sigTermHandler(int)
{
printf("Got SIGTERM. Cleaning up. Quitting...\n");
keepRunning = false;
}
void sigHupHandler(int)
{
printf("Got SIGHUP. Ignoring.\n");
}
int main(int argc, const char* argv[])
{
// Handle Ctrl+C and quit gracefully
signal(SIGINT, sigIntHandler);
// Handle SIGTERM (15) and quit gracefully
signal(SIGTERM, sigTermHandler);
// Handle SIGHUP: Ignore
signal(SIGHUP, sigHupHandler);
if (argc != 3)
{
usage(argv[0]);
exit(0);
}
const char* mcastGroup = argv[1];
int portNum = std::atoi(argv[2]);
struct sockaddr_in addr;
int fd, nbytes;
struct ip_mreq mreq;
char msgbuf[MSG_BUF_SIZE];
// Create a UDP socket
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket");
exit(1);
}
// Allow multiple sockets to use the same PORT number
// We use both SO_REUSEADDR & SO_REUSEPORT to make sure that it works on different systems
unsigned int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0)
{
perror("Reusing ADDR failed");
exit(1);
}
/*
// http://stackoverflow.com/questions/14388706/socket-options-so-reuseaddr-and-so-reuseport-how-do-they-differ-do-they-mean-t
if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes)) < 0)
{
perror("Reusing PORT failed");
exit(1);
}
*/
// Timeout on the listening so the app could quit gracefully
struct timeval tv;
tv.tv_sec = 2; // 2 seconds
tv.tv_usec = 1000; // 1000 microseconds
if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0)
{
perror("Could not set timeout on the RECV");
exit(1);
}
// Set up destination address
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
// Differs from the publisher that must use mcastGroup/HELLO_GROUP
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(portNum);
// Bind to receive address
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
perror("bind");
exit(1);
}
// Use setsockopt() to request the kernel to join the multicast group
mreq.imr_multiaddr.s_addr = inet_addr(mcastGroup);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
perror("Cannot join multicast group");
exit(1);
}
// Keep sending until get a signal to terminate
socklen_t addrlen = sizeof(addr);
while (keepRunning)
{
if ((nbytes = recvfrom(fd, msgbuf, MSG_BUF_SIZE, 0, (struct sockaddr*)&addr, &addrlen)) < 0)
{
// Try again because we got timeout
// We don't want to print timeout every single time ("Resource temporarily unavailable")
if (errno == EAGAIN) continue;
// Handle so the app doesn't die with error = 1 because SIGNIT/SIGHUP/SIGTERM were already handled
if (errno == EINTR)
{
perror("recvfrom was interrupted");
continue;
}
perror("recvfrom");
exit(1);
}
// Make sure we have no issues before printing/processing data
if (nbytes > 0)
{
// Don't use puts! Use std::cout.write with nbytes for safety!
std::cout.write(msgbuf, nbytes);
std::cout << std::endl;
// Reset
nbytes = 0;
}
// https://www.securecoding.cert.org/confluence/pages/viewpage.action?pageId=6619179
// Reset errno so you can keep receiving. Otherwise it will always be EAGAIN or != 0
if (errno != 0) errno = 0;
}
return 0;
}