Multicasting is a great way to communicate with multiple computers without the necessity to know the IP of the computer you want to communicate as a prior knowledge.
For example it can be put into good use in device discovery. A client can multicast on a particluar set of IP and port to notify it’s online. The server can then find out the IP and port the client is from.
However, in many cases multicast may be blocked due to security reasons, thus it will more likely to be working reliabliy in you local network.
Now, let us move on to the programming part.
Headers and libraries
Potentially there are quite a lot headers and libraries you need, without them your program will not compile.
#include <Winsock2.h> // Winsock2 header
#include <Ws2tcpip.h> // This header lets you handle address info
#pragma comment(lib,"WS2_32") // Links to winsock2 library
These are the bare minimum you need. You will likely need more than these, but it is enough for this tutorial.
WSAStartup
WSAStartup is the first function you call.
//WSAStartup
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
std::cout << "WSAStartup function failed with error: <" << iResult << ">\n";
return -1;
}
This function initiates the winsock2 library. The argument passed, “MAKEWORD(2, 2)” is to specify version 2.2(Most recent) of winsock is used.
WSACleanup();
Always call WSACleanup when you finish using the winsock2 library.
Creating the send socket
Now let’s start create the sockets on the sender side. This socket is almost identical to a unicast socket, the only difference is that you are sending to a multicast address. Namely, IP adress from 224.0.0.0 to 239.255.255.255.
// Create socket
SOCKET Socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0);
// An IPV4 udp blocking socket, I will likely write a tutorial on overlapped receiving socket in the future.
if (Socket == INVALID_SOCKET)
{
std::cout<< "Can not create socket: <"<< WSAGetLastError()<<">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
//Set target address
sockaddr_in TargetAddr;
memset(&TargetAddr, 0, sizeof(TargetAddr));
TargetAddr.sin_family = AF_INET;
if (inet_pton(AF_INET, (PCSTR)(IP.c_str()), &TargetAddr.sin_addr.s_addr) < 0) {
std::cout << "Multicast failed set join group\n";
closesocket(Socket);
WSACleanup();
return -1;
}
TargetAddr.sin_port = htons(Port);
Finally, you send it with
//Send
std::string message = "Hello";
iResult = sendto(
Socket,
message.c_str(),
message.length(),
0,
(struct sockaddr*)&TargetAddr,
sizeof(TargetAddr)
);
if (iResult == SOCKET_ERROR) {
std::cout << "Sendto failed with error: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
}
The receive socket
Now let’s create a receive side socket. The receive socket’s difference to a unicast one is the addition of joining an IGMP group. After creating the socket we bind it to an address.
// Create socket same a sthe send side
SOCKET Socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0);
if (Socket == INVALID_SOCKET)
{
std::cout << "Can not create socket: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
// Allow any address, "Port" is the one you set in TargetAddr on the send side
memset(&AllowAddr, 0, sizeof(AllowAddr));
AllowAddr.sin_family = AF_INET;
AllowAddr.sin_addr.s_addr = htonl(INADDR_ANY);
AllowAddr.sin_port = htons(Port);
// Bind socket
if (bind(Socket, (struct sockaddr*)&AllowAddr, sizeof(AllowAddr)) < 0) {
std::cout << "Multicast failed to bind socket\n";
closesocket(Socket);
WSACleanup();
return -1;
}
Now we join the IGMP group.
// Membership setting
// Ip is the multicast IP we want to receive from
if (inet_pton(AF_INET, (PCSTR)(IP.c_str()), &JoinReq.imr_multiaddr.s_addr) < 0) {
std::cout << "IP invalid\n";
closesocket(Socket);
WSACleanup();
return -1;
}
// This can be used to restrict to only receive form particular sender
JoinReq.imr_interface.s_addr = htonl(INADDR_ANY);
// Join membership
if ((setsockopt(Socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&JoinReq, sizeof(JoinReq))) < 0)
{
std::cout << "Multicast join membership fail. Error code: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
Finally, we receive with…
sockaddr_in ClientAddr;
int addrlen = sizeof(ClientAddr);
int nbytes = recvfrom(
Socket,
ReceivedMessage,
UDP_MAX_SIZE,
0,
(struct sockaddr*)&ClientAddr,
&addrlen
);
if (nbytes < 0) {
std::cout << "Receive fail. Error code: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
Now we can multicast send and receive.
Working example
Sending side.
#include <iostream>
#include <string>
#include <Winsock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib,"WS2_32")
int Port = 8910;
std::string IP = "234.5.6.7";
int optval = 0;
int main()
{
//WSAStartup
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
std::cout << "WSAStartup function failed with error: <" << iResult << ">\n";
WSACleanup();
return -1;
}
// Create socket
SOCKET Socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0);
// An IPV4 udp blocking socket, I will write a tutorial on overlapped socket in the future.
if (Socket == INVALID_SOCKET)
{
std::cout << "Can not create socket: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
// Allow reuse of port
optval = 1;
if ((setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval))) < 0)
{
std::cout << "Socket set SO_REUSEADDR fail\n";
closesocket(Socket);
WSACleanup();
return -1;
}
//Set target address
sockaddr_in TargetAddr;
memset(&TargetAddr, 0, sizeof(TargetAddr));
TargetAddr.sin_family = AF_INET;
if (inet_pton(AF_INET, (PCSTR)(IP.c_str()), &TargetAddr.sin_addr.s_addr) < 0) {
std::cout << "Multicast failed set join group\n";
closesocket(Socket);
WSACleanup();
return -1;
}
TargetAddr.sin_port = htons(Port);
std::string SendMessage;
//Send
while (1) {
std::cin >> SendMessage;
iResult = sendto(
Socket,
SendMessage.c_str(),
SendMessage.length(),
0,
(struct sockaddr*)&TargetAddr,
sizeof(TargetAddr)
);
if (iResult == SOCKET_ERROR) {
std::cout << "Sendto failed with error: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
}
if (SendMessage.compare("bye") == 0) {
break;
}
}
closesocket(Socket);
WSACleanup();
return 1;
}
Receive side.
#include <iostream>
#include <string>
#include <Winsock2.h>
#include <Ws2tcpip.h>
#pragma comment(lib,"WS2_32")
int Port = 8910;
#define UDP_MAX_SIZE 65535
std::string IP = "234.5.6.7";
int optval = 0;
int main()
{
sockaddr_in AllowAddr;
ip_mreq JoinReq;
//WSAStartup
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
std::cout << "WSAStartup function failed with error: <" << iResult << ">\n";
WSACleanup();
return -1;
}
// Create socket
SOCKET Socket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, 0);
if (Socket == INVALID_SOCKET)
{
std::cout << "Can not create socket: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
// Allow reuse of port
optval = 1;
if ((setsockopt(Socket, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval))) < 0)
{
std::cout << "Socket set SO_REUSEADDR fail\n";
closesocket(Socket);
WSACleanup();
return -1;
}
// Allow any address
memset(&AllowAddr, 0, sizeof(AllowAddr));
AllowAddr.sin_family = AF_INET;
AllowAddr.sin_addr.s_addr = htonl(INADDR_ANY);
AllowAddr.sin_port = htons(Port);
// Bind socket
if (bind(Socket, (struct sockaddr*)&AllowAddr, sizeof(AllowAddr)) < 0) {
std::cout << "Multicast failed to bind socket\n";
closesocket(Socket);
WSACleanup();
return -1;
}
// Membership setting
if (inet_pton(AF_INET, (PCSTR)(IP.c_str()), &JoinReq.imr_multiaddr.s_addr) < 0) {
std::cout << "IP invalid\n";
closesocket(Socket);
WSACleanup();
return -1;
}
// This can be used to restrict to only receive form particular sender
JoinReq.imr_interface.s_addr = htonl(INADDR_ANY);
// Join membership
if ((setsockopt(Socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&JoinReq, sizeof(JoinReq))) < 0)
{
std::cout << "Multicast join membership fail. Error code: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
char ReceivedIP[46] = { 0 };
char ReceivedMessage[UDP_MAX_SIZE + 1] = { 0 };
//Receive
while (1) {
sockaddr_in ClientAddr;
int addrlen = sizeof(ClientAddr);
int nbytes = recvfrom(
Socket,
ReceivedMessage,
UDP_MAX_SIZE,
0,
(struct sockaddr*)&ClientAddr,
&addrlen
);
if (nbytes < 0) {
std::cout << "Receive fail. Error code: <" << WSAGetLastError() << ">\n";
closesocket(Socket);
WSACleanup();
return -1;
}
inet_ntop(AF_INET, &ClientAddr.sin_addr, (PSTR)ReceivedIP, 46);
std::cout << "Received from: " << ReceivedIP << ", " << ntohs(ClientAddr.sin_port) << "\n";
std::cout << ReceivedMessage << "\n";
if (strcmp("bye", ReceivedMessage) == 0) {
break;
}
memset(ReceivedMessage, 0, UDP_MAX_SIZE);
}
closesocket(Socket);
WSACleanup();
return 1;
}