C++ winsock2 multicasting

Scott Jin
5 min readJul 22, 2023

--

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;
}

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Scott Jin
Scott Jin

Written by Scott Jin

Graduate student from Taiwan in Computer Science at the University of California, Riverside. Passionate about HPC, ML, and embedded software development.

No responses yet

Write a response