< Multicasting & IPv6 | Multicasting Main | ATM & Multicasting >
What do we have in this chapter 9 part 5?
|
Reliable Multicasting (RM)
The reliable multicasting provider is available on Windows XP when the Microsoft Message Queuing (MSMQ) component is installed. Reliable multicasting is a protocol built over IPv4 multicasting. Currently, it does not support IPv6. The reliable multicast implementation on Windows XP is based on the Pragmatic General Multicasting (PGM) specification. The protocol is NAK-based, meaning that all receivers of the data send notice to the sender only when it detects that a packet is missing. If the protocol was acknowledgment-based, like TCP, you would have cases in which hundreds or possibly thousands of receivers sent a packet to the sender indicating they successfully received a packet, which would flood the sender's network. This section provides only an overview of how the protocol is implemented so you can gain a basic understanding of how reliable multicasting is provided. For a complete specification of the PGM protocol, consult the IETF draft: draft-speakman-pgm-spec-06.txt. The reliable multicast protocol works by the sender caching data sent to the receivers for a specified time period, which is known as the send window. This window is periodically moved forward, discarding the oldest data within the window to make room for new data the sender sends. There are three variables that make up the send window: the send rate, the size of the window (in bytes), and the size of the window in seconds (for example, how long the data remains in the window before being purged). The reliable multicast driver establishes certain default values for the window size that can be overridden (because the default values are rather low). The protocol also contains a sequence number in the data packets so that if a receiver detects that it has missed a particular sequence, it may send a NAK to the sender, who will retransmit the missing data. Note that it is possible that a receiver NAKs for data that was recently purged from the send window. In this case, the receiver's “session” will be reset because of unrecoverable data loss, this is analogous to a TCP session being reset due to an ACK timeout or similar error. In terms of the Winsock provider for reliable multicasting, there are a couple of important issues. First, there is a stream provider (SOCK_STREAM) as well as a reliable datagram provider (SOCK_RDM). The protocol value that is passed to the socket creation functions is IPPROTO_RM. The socket options specific to this protocol are contained in WSRM.H. In the following sections we will look at how to create a reliable multicast sender as well as a receiver.
Reliable Sender
Establishing the reliable multicast sender is a simple process. The following list contains the necessary steps.
|
As with IP multicasting, it is necessary to set the outgoing interface in the case of a multihomed computer. Also note that no real connection is taking place between the sender and receiver. The connect() that the sender called simply associates the destination multicast address with the socket. The following code example illustrates setting up a reliable multicast sender.
SOCKET s;
ULONG sendif;
SOCKADDR_IN localif, multi;
char buf[1024];
int buflen=1024;
s = socket(AF_INET, SOCK_RDM, IPPROTO_RM);
// Bind to INADDR_ANY
localif.sin_family = AF_INET;
localif.sin_port = htons(0);
localif.sin_addr.s_addr = htonl(INADDR_ANY);
bind(s, (SOCKADDR *)&localif, sizeof(localif));
// Set the outgoing interface
sendif = inet_addr("157.124.22.104");
setsockopt(s, IPPROTO_RM, RM_SET_SEND_IF, (char *)&sendif, sizeof(sendif));
// Connect the socket to the multicast destination
multi.sin_family = AF_INET;
multi.sin_port = htons(5150);
multi.sin_addr.s_addr = inet_addr("234.5.6.7");
connect(s, (SOCKADDR *)&multi, sizeof(multi));
// Send the data
send(s, buf, buflen, 0);
// Close up the session
closesocket(s);
In this example, we create a socket of type SOCK_RDM. This is similar to SOCK_DGRAM because it provides message-oriented semantics but also implies reliability. Also, the local port supplied to the bind call is not significant. After the session is created, the sender may begin to send data.
By default, the reliable multicast driver sets up the default window size to buffer sent data. At some interval, the window is advanced by an increment, discarding the oldest data to make room for newer data. This window plays an important role in how reliable the data is. For example, a high data rate sender may receive many NAKs for lost data from receivers on a lower bandwidth or a congested network. As a result, if the send window is fairly small, those receivers may not be able to keep up. To prevent this, the sender may resize the send window to better suit the application's needs. This is done via the RM_RATE_WINDOW_SIZE socket option. The input parameter is a RM_SEND_WINDOW structure defined as:
typedef struct _RM_SEND_WINDOW
{
ULONG RateKbitsPerSec;
ULONG WindowSizeInMSecs;
ULONG WindowSizeInBytes;
} RM_SEND_WINDOW;
The RateKbitsPerSec specifies the data rate that the sender will be sending at. By default, the send rate is 56 Kbps. WindowSizeInMSecs specifies how long (in milliseconds) the data is to remain in the window before being purged to make room for new data. The last field, WindowSizeInBytes, represents how many bytes can be stored in the window before old data has to be purged to make room for new data. When setting this option, the following equation must hold true: WindowSizeInBytes = (RateKbitsPerSec/8) x WindowSizeInMSecs.
This option must be set before connecting the socket to the multicast destination address, all sender socket options must be set before issuing the connect call.
FEC is a method by which H parity packets are created out of K original data packets (for a total of N packets) so that the receiver can reconstruct the K original packets out of any K packets out of N received. For example, if for each four original data packets an additional four parity packets were created (for a total of eight packets), then the receiver needs to receive only any four of the eight to reconstruct the original data.
The algorithm employs packet-level Reed Solomon Erasure correcting techniques. This means that when FEC is enabled, more packets are hitting the wire as a number of parity packets are generated in addition to the data, but receivers typically lose a small number of packets at a time. If the receiver loses one packet out of the N generated, it can still recover the original data packets, thus preventing a NAK for the lost data and wait for the retransmission.
There are two operational modes in which FEC is enabled: pro-active and on-demand. With pro-active, the sender always computes parity packets for all data sent. The drawback is that computing parity packets can be a processor-intensive calculation. The second mode is on-demand, which means the sender sends data normally until a receiver sends a NAK, at which point the repair data is sent as a FEC data. In general, pro-active FEC is used when a large number of receivers are on lossy networks and the expected retransmission of lost data will become too large. On-demand is a trade off between reliability and network overhead. It is useful when several clients on the same network lose different packets belonging to the same FEC group. In this case, each receiver NAKs for the lost packet, at which point the sender will send a single FEC repair packet that will satisfy all of the clients' NAK request.
To enable FEC on a socket, the RM_USE_FEC socket option is used. This option must be set before connecting the socket. The input parameter is an RM_FEC_INFO structure that is defined as:
typedef struct _RM_FEC_INFO
{
USHORT FECBlockSize;
USHORT FECProActivePackets;
UCHAR FECGroupSize;
BOOLEAN fFECOnDemandParityEnabled;
} RM_FEC_INFO;
The FECBlockSize field indicates the maximum number of packets generated. This field is the N packets discussed earlier (for example, original data packets plus parity packets). However, the current implementation ignores this field and instead relies on the sum of the FECProActivePackets and FECGroupSize fields to determine the block size. FECProActivePackets indicates the number of parity packets to be generated—this is the H parity packets generated. FECGroupSize is the number of original data packets used to generate parity information from, the original data packets, K. Finally, fFECOnDemandParityEnabled indicates whether on-demand FEC requests should be allowed.
Setting up a reliable multicast receiver consists of the following five steps.
Create a reliable multicast socket.
Bind the socket specifying the multicast group address to join the session on.
If the receiver wishes to listen on specific interfaces (as opposed to listening on all interfaces), call setsockopt() and RM_ADD_RECEIVE_IF to add each interface.
Call listen().
Wait on an accept() function.
There are a couple of important differences to note with the receiver. First, when binding the socket, the local interface is not specified. Instead, the multicast group the receiver wishes to join on is given. If the port number specified in the SOCKADDR_IN structure is zero, any reliable multicast session for the specified multicast group is accepted. Otherwise, if a specific port is given, then a reliable multicast session will be joined only if it matches the multicast group and the port number.
The second difference is that the receiver must add which interfaces to listen for incoming sessions on. Because the bind call is used to specify the multicast session to join, it is necessary to indicate which local interfaces should listen on for incoming sessions. By default, the socket will listen for incoming connections on any local interface. If the application wishes to accept sessions from a specific interface it must call RM_ADD_RECEIVE_IF for each local interface. Of course, this is important only on multihomed computers. The argument to the RM_ADD_RECEIVE_IF is the 32-bit network-byte order local interface to listen on. This option may be called multiple times. The option RM_DEL_RECEIVE_IF can be used to remove an interface from the listening set; however, it may be called only if RM_ADD_RECEIVE_IF has been invoked previously.
When a receiver joins a reliable multicast session, it does not have to start receiving at the beginning of the sender's data transmission. As a part of each session, the oldest data that may be requested for repair is advertised. Therefore, if the client joins the session after the sender has started transmitting data, the client may NAK for data back to the advertised sequence number. This is known as a late join. The sender can actually limit how much of its send window may be NAKed by late joiners (via the RM_LATEJOIN option). Of course, this is totally transparent to the application. The reliable multicast provider will automatically NAK for all available data when the session is joined. Once the session is joined, if data is lost and cannot be repaired, the session will be aborted. If, on the other hand, the sender closes the session, when all the remaining data has been delivered to the application the next receive operation will fail with the error WSEDISCONN.
The following code sample illustrates how to set up a reliable multicast receiver:
SOCKET s, ns;
SOCKADDR_IN multi, safrom;
ULONG localif;
char buf[1024];
int buflen=1024, fromlen, rc;
s = socket(AF_INET, SOCK_RDM, IPPROTO_RM);
multi.sin_family = AF_INET;
multi.sin_port = htons(5150);
multi.sin_addr.s_addr = inet_addr("234.5.6.7");
bind(s, (SOCKADDR *)&multi, sizeof(multi));
listen(s, 10);
localif = inet_addr("157.124.22.104");
setsockopt(s, IPPROTO_RM, RM_ADD_RECEIVE_IF, (char *)&localif, sizeof(localif));
fromlen = sizeof(safrom);
ns = accept(s, (SOCKADDR *)&safrom, &fromlen);
closesocket(s); // Don't need to listen anymore
// start receiving data . . .
while (1) {
rc = recv(ns, buf, buflen, 0);
if (rc == SOCKET_ERROR) {
if (WSAGetLastError() == WSAEDISCON)
break;
else {
// An unexpected error
}
}
}
closesocket(ns);
The following program example tries to demonstrate the reliable multicasting.
Take note that for the SOCK_RDM and IPPROTO_RM to work, you must have the MSMQ (Microsoft Message Queuing) installed.
Add the following source code.
// File:
// multicastexamplesrc.cpp - this file
// resolve.cpp - common name resolution routines
// resolve.h - header file for common name resolution routines
// Purpose:
// This sample illustrates the reliable multicast protocol. The client is
// simple, create a reliable multicast socket, set the receive interfaces,
// and wait for a session. After the session is joined, receive data on the
// accepted session. For the server, the window size and other options may
// be specified on the command line.
//
// Command line options
// multicastexamples
// -fb int FEC block size
// -fg int FEC group size
// -fo Enable on-demand FEC
// -fp int Set FEC pro-active count
// -i Local interface
// Sender: This specifies the send interface
// Receiver: This is the listen interface - may be specified multiple times
// -j int Late join percentage (sender only)
// -m str Dotted decimal multicast IP address to join
// -n int Number of messages to send/receive
// -p int Port number to use
// -s Act as server (send data); otherwise receive data.
// -t int Set multicast TTL
// -wb int Set the send window size in bytes
// -wr int Set the send window rate in bytes/second
// -ws int Set the send window size in seconds
// -z int Size of the send/recv buffer
//
// Link to ws2_32.lib
#include <winsock2.h>
#include <ws2tcpip.h>
#include <wsrm.h>
#include <windows.h>
#include "resolve.h"
#include <stdio.h>
#define MCASTADDRV4 "224.0.0.255"
#define MCASTPORT "25000"
#define BUFSIZE 1024
#define DEFAULT_COUNT 500
#define DEFAULT_TTL 8
#define MAX_LOCAL_INTERFACES 64
BOOL bSender=FALSE, // Act as sender?
bUseFec=FALSE, // Enable FEC
bFecOnDemand=FALSE, // Enable FEC on demand
bSetSendWindow=FALSE;
// For the SOCK_RDM & IPPROTO_RM to work, you must have the
// MSMQ (Microsoft Message Queuing ) installed lor!
// http://support.microsoft.com/kb/256096
// int gSocketType=SOCK_DGRAM,
int gSocketType=SOCK_RDM, // Reliable datagram
gProtocol=IPPROTO_RM, // Reliable multicast protocol
// gProtocol=IPPROTO_UDP,
gCount=DEFAULT_COUNT, // Number of messages to send/receive
gTtl=DEFAULT_TTL, // Multicast TTL value
gBufferSize=BUFSIZE; // Buffer size for send/recv
int gInterfaceCount=0; // Number of local interfaces specified
char *gListenInterface[MAX_LOCAL_INTERFACES], // Set of listening interfaces
*gMulticast=MCASTADDRV4, // Multicast group to join
*gPort=MCASTPORT; // Port number to use
// Window size parameters
int gWindowRateKbitsSec=0, gWindowSizeBytes=0, gWindowSizeMSec=0;
// FEC parameters
int gFecBlockSize=8, gFecGroupSize=16, gFecProActive=8;
// Late join option
int gLateJoin=-1;
// Function: usage
// Description: Print usage information and exit.
int usage(char *progname)
{
printf("Usage: %s -s -m str -p int -i str -l -n int\n", progname);
printf(" -fb int FEC block size\n");
printf(" -fg int FEC group size\n");
printf(" -fo Enable on-demand FEC\n");
printf(" -fp int Set FEC pro-active count\n");
printf(" -i Local interface\n");
printf(" Sender: This specifies the send interface\n");
printf(" Receiver: This is the listen interface - may be specified multiple times\n");
printf(" -j int Late join percentage (sender only)\n");
printf(" -m str Dotted decimal multicast IP address to join\n");
printf(" -n int Number of messages to send/receive\n");
printf(" -p int Port number to use. The default port is: %s\n", MCASTPORT);
printf(" -s Act as server (send data), otherwise receive data.\n");
printf(" -t int Set multicast TTL\n");
printf(" -wb int Set the send window size in bytes\n");
printf(" -wr int Set the send window rate in bytes/second\n");
printf(" -ws int Set the send window size in seconds\n");
printf(" -z int Size of the send/recv buffer\n");
printf("\n");
return 0;
}
// Function: ValidateArgs
// Description: Parse the command line arguments and set some global flags depending on the values
void ValidateArgs(int argc, char **argv)
{
int i;
for(i=1; i < argc ;i++)
{
if ((argv[i][0] == '-') || (argv[i][0] == '/'))
{
switch (tolower(argv[i][1]))
{
case 'f': // Use fec
if (strlen(argv[i]) != 3)
usage(argv[0]);
bUseFec = TRUE;
switch (tolower(argv[i][2]))
{
case 'b': // FEC block size
if (i+1 >= argc)
usage(argv[0]);
gFecBlockSize = atol(argv[++i]);
break;
case 'g': // FEC group size
if (i+1 >= argc)
usage(argv[0]);
gFecGroupSize = atol(argv[++i]);
break;
case 'o': // FEC on demand
bFecOnDemand = TRUE;
break;
case 'p': // Pro active FEC count
if (i+1 >= argc)
usage(argv[0]);
gFecProActive = atol(argv[++i]);
break;
default:
usage(argv[0]);
break;
}
break;
case 'i': // local interface to use
if (i+1 >= argc)
usage(argv[0]);
gListenInterface[gInterfaceCount++] = argv[++i];
break;
case 'j': // Late join value
if (i+1 >= argc)
usage(argv[0]);
gLateJoin = atol(argv[++i]);
if (gLateJoin > SENDER_MAX_LATE_JOINER_PERCENTAGE)
{
gLateJoin = SENDER_MAX_LATE_JOINER_PERCENTAGE;
printf("Exceeded maximum late join value (%d%%)!\n", gLateJoin);
printf(" Setting value to maximum allowed\n");
}
break;
case 'm': // multicast group to join
if (i+1 >= argc)
usage(argv[0]);
gMulticast = argv[++i];
break;
case 'n': // Number of messages to send/recv
if (i+1 >= argc)
usage(argv[0]);
gCount = atoi(argv[++i]);
break;
case 'p': // Port number to use
if (i+1 >= argc)
usage(argv[0]);
gPort = argv[++i];
break;
case 's': // sender
bSender = TRUE;
break;
case 't': // Multicast ttl
if (i+1 >= argc)
usage(argv[0]);
gTtl = atoi(argv[++i]);
break;
case 'w': // Send window size
if ((i+1 >= argc) || (strlen(argv[i]) != 3))
usage(argv[0]);
bSetSendWindow = TRUE;
switch (tolower(argv[i][2]))
{
case 'b': // Window size in bytes
gWindowSizeBytes = atol(argv[++i]);
break;
case 's': // Window size in seconds
gWindowSizeMSec = atol(argv[++i]) * 1000;
break;
case 'r': // Window size in bytes/sec
gWindowRateKbitsSec = (atol(argv[++i])/1000) * 8;
break;
default:
usage(argv[0]);
break;
}
break;
case 'z': // Buffer size for send/recv
if (i+1 >= argc)
usage(argv[0]);
gBufferSize = atol(argv[++i]);
break;
default:
usage(argv[0]);
break;
}
}
}
return;
}
// Function: SetSendInterface
// Description: This routine sets the sending interface. This option may only be set on senders
int SetSendInterface(SOCKET s, struct addrinfo *iface)
{
int rc;
// Set the send interface
rc = setsockopt(
s,
IPPROTO_RM,
RM_SET_SEND_IF,
(char *)&((SOCKADDR_IN *)iface->ai_addr)->sin_addr.s_addr,
sizeof(ULONG)
);
if (rc == SOCKET_ERROR)
{
printf("setsockopt() failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Set sending interface to: ");
PrintAddress(iface->ai_addr, iface->ai_addrlen);
printf("\n");
}
return rc;
}
// Function: AddReceiveInterface
// Description:
// This routine adds the given interface to the receiving interface list. This option is valid only for receivers
int AddReceiveInterface(SOCKET s, struct addrinfo *iface)
{
int rc;
rc = setsockopt(
s,
IPPROTO_RM,
RM_ADD_RECEIVE_IF,
(char *)&((SOCKADDR_IN *)iface->ai_addr)->sin_addr.s_addr,
sizeof(ULONG)
);
if (rc == SOCKET_ERROR)
{
printf("setsockopt(): RM_ADD_RECEIVE_IF failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Adding receive interface: ");
PrintAddress(iface->ai_addr, iface->ai_addrlen);
printf("\n");
}
return rc;
}
// Function: SetMulticastTtl
// Description: This routine sets the multicast TTL value for the socket.
int SetMulticastTtl(SOCKET s, int af, int ttl)
{
int rc;
// Set the TTL value
rc = setsockopt(
s,
IPPROTO_RM,
RM_SET_MCAST_TTL,
(char *)&ttl,
sizeof(ttl)
);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "SetMulticastTtl: setsockopt() failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Set multicast ttl to: %d\n", ttl);
}
return rc;
}
// Function: SetFecParamters
// Description:
// This routine sets the requested FEC parameters on a sender socket.
// A client does not have to do anything special when the sender enables or disables FEC
int SetFecParameters(SOCKET s, int blocksize, int groupsize, int ondemand, int proactive)
{
RM_FEC_INFO fec;
int rc;
memset(&fec, 0, sizeof(fec));
fec.FECBlockSize = (USHORT) blocksize;
fec.FECProActivePackets = (USHORT) proactive;
fec.FECGroupSize = (UCHAR) blocksize;
fec.fFECOnDemandParityEnabled = (BOOLEAN)ondemand;
rc = setsockopt(s, IPPROTO_RM, RM_USE_FEC, (char *)&fec, sizeof(fec));
if (rc == SOCKET_ERROR)
{
printf("Setting FEC parameters:\n");
printf(" Block size: %d\n", blocksize);
printf(" Pro active: %d\n", proactive);
printf(" Group size: %d\n", groupsize);
printf(" On demand : %s\n", (ondemand ? "TRUE" : "FALSE"));
}
else
{
fprintf(stderr, "setsockopt(): RM_USE_FEC failed with error code %d\n", WSAGetLastError());
}
return rc;
}
// Function: SetWindowSize
// Description:
// This routine sets the window size for the sending socket which includes
// the byte rate, window size, and window time parameters. Before setting
// the parameters a simple calculation is performed to determine whether
// the values passed in make sense. If they don't an error message is
// displayed but the set is attempted anyway. If the values don't jive
// then the option will fail with WSAEINVAL and the default window
// rate, size, and time are used instead.
int SetWindowSize(SOCKET s, int windowsize, int windowtime, int windowrate)
{
RM_SEND_WINDOW window;
int rc;
memset(&window, 0, sizeof(window));
if (windowsize != ((windowrate/8) * windowtime))
{
printf("Window parameters don't compute!\n");
}
window.RateKbitsPerSec = windowrate;
window.WindowSizeInMSecs = windowtime;
window.WindowSizeInBytes = windowsize;
rc = setsockopt(s, IPPROTO_RM, RM_RATE_WINDOW_SIZE, (char *)&window, sizeof(window));
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "setsockopt(): RM_RATE_WINDOW_SIZE failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Setting window parameters:\n");
printf(" Rate (kbits/sec): %d\n", windowrate);
printf(" Size (bytes) : %d\n", windowsize);
printf(" Time (msec) : %d\n", windowtime);
}
return rc;
}
// Function: SetLateJoin
// Description:
// This option sets the latejoin value. This specifies the percentage of the
// window that a receiver can NAK in the event the receiver picked up the
// session in the middle of the sender's transmission. This option is set
// on the sender side and is advertised to the receivers when the session is joined.
int SetLateJoin(SOCKET s, int latejoin)
{
int rc;
rc = setsockopt(s, IPPROTO_RM, RM_LATEJOIN, (char *)&latejoin, sizeof(latejoin));
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "setsockopt(): RM_LATEJOIN failed with error code %d\n", WSAGetLastError());
}
else
{
printf("Setting latejoin: %d\n", latejoin);
}
return rc;
}
// Function: main
// Description:
// Parse the command line arguments, load the Winsock library,
// create a socket and join the multicast group. If set as a
// sender then begin sending messages to the multicast group;
// otherwise, call recvfrom() to read messages send to the group
int main(int argc, char **argv)
{
WSADATA wsd;
SOCKET s, sc;
SOCKADDR_STORAGE remote;
struct addrinfo *resmulti=NULL, *resif=NULL, *resbind=NULL;
char *buf=NULL;
int remotelen, err, rc, i=0;
// If no arguments supplied, exit and shows the usage
if(argc < 2)
{
usage(argv[0]);
exit(1);
}
// Parse the command line
ValidateArgs(argc, argv);
// Load Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
{
printf("WSAStartup() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("WSAStartup() is OK!\n");
// Resolve the multicast address
resmulti = ResolveAddress(gMulticast, gPort, AF_INET, 0, 0);
if (resmulti == NULL)
{
fprintf(stderr, "Unable to convert multicast address '%s': %d\n", gMulticast, WSAGetLastError());
return -1;
}
else
printf("ResolveAddress() should be fine!\n");
// Create the socket. In Winsock 1 you don't need any special flags to indicate multicasting.
s = socket(resmulti->ai_family, gSocketType, gProtocol);
if (s == INVALID_SOCKET)
{
printf("socket() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("socket() is working...\n");
printf("socket handle = 0x%p\n", s);
// Allocate the send/receive buffer
buf = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, gBufferSize);
if (buf == NULL)
{
fprintf(stderr, "HeapAlloc() failed with error code %d\n", GetLastError());
return -1;
}
else
printf("HeapAlloc() for buffer is OK!\n");
if (bSender)
{
// Bind to the wildcard address
resbind = ResolveAddress(NULL, gPort, AF_INET, 0, 0);
if (resbind == NULL)
{
fprintf(stderr, "Unable to obtain bind address!\n");
return -1;
}
else
printf("ResolveAddress() should be fine!\n");
// Bind the socket
rc = bind(s, resbind->ai_addr, resbind->ai_addrlen);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "bind() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("bind() is OK!\n");
freeaddrinfo(resbind);
// If specified, set the send interface
if (gInterfaceCount == 1)
{
resif = ResolveAddress(gListenInterface[0], gPort, AF_INET, 0, 0);
if (resif == NULL)
{
return -1;
}
else
printf("Setting the interface count...\n");
rc = SetSendInterface(s, resif);
freeaddrinfo(resif);
// Set the TTL to something else. The default TTL is one.
rc = SetMulticastTtl(s, resmulti->ai_family, gTtl);
if (rc == SOCKET_ERROR)
{
return -1;
}
else
printf("Setting the TTL...\n");
}
// If specified set the late joiner option
if (gLateJoin != -1)
{
printf("Setting the late joiner...\n");
SetLateJoin(s, gLateJoin);
}
// If specified set the window parameters
if (bSetSendWindow)
{
printf("Setting the window size...\n");
SetWindowSize(s, gWindowSizeBytes, gWindowSizeMSec, gWindowRateKbitsSec);
}
// If specified set the FEC parameters
if (bUseFec == TRUE)
{
printf("Setting the FEC...\n");
SetFecParameters(s, gFecBlockSize, gFecGroupSize, bFecOnDemand, gFecProActive);
}
// Connect the socket to the multicast group the session is to be on
rc = connect(s, resmulti->ai_addr, resmulti->ai_addrlen);
if (rc == SOCKET_ERROR)
{
printf("connect() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("connect() looks fine!\n");
memset(buf, '$', gBufferSize);
// Send some data
for(i=0; i < gCount ; i++)
{
rc = send(s, buf, gBufferSize, 0);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "send() failed with error code %d\n", WSAGetLastError());
return -1;
}
printf("Sent %d bytes\n", rc);
}
}
else
{
// Bind the socket to the multicast address on which the session will take place
rc = bind(s, resmulti->ai_addr, resmulti->ai_addrlen);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "bind() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("bind() is pretty fine!\n");
printf("Binding to ");
PrintAddress(resmulti->ai_addr, resmulti->ai_addrlen);
printf("\n");
// Add each supplied interface as a receive interface
if (gInterfaceCount > 0)
{
for(i=0; i < gInterfaceCount ;i++)
{
resif = ResolveAddress(gListenInterface[i], "0", AF_INET, 0, 0);
if (resif == NULL)
{
return -1;
}
rc = AddReceiveInterface(s, resif);
freeaddrinfo(resif);
}
}
// Listen for sessions
rc = listen(s, 1);
if (rc == SOCKET_ERROR)
{
fprintf(stderr, "listen() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("listen() is OK!\n");
// Wait for a session to become available
remotelen = sizeof(remote);
sc = accept(s, (SOCKADDR *)&remote, &remotelen);
if (sc == INVALID_SOCKET)
{
fprintf(stderr, "accept() failed with error code %d\n", WSAGetLastError());
return -1;
}
else
printf("accept() is OK!\n");
printf("Join multicast session from: ");
PrintAddress((SOCKADDR *)&remote, remotelen);
printf("\n");
while (1)
{
// Receive data until an error or until the session is closed
rc = recv(sc, buf, gBufferSize, 0);
if (rc == SOCKET_ERROR)
{
if ((err = WSAGetLastError()) != WSAEDISCON)
{
fprintf(stderr, "recv() failed with error code %d\n", err);
}
break;
}
else
{
printf("received %d bytes\n", rc);
}
}
// Close the session socket
if(closesocket(sc) == 0)
printf("closesocket(sc) is OK!\n");
else
printf("closesocket(sc) failed with error code %d\n",WSAGetLastError());
}
// Clean up
freeaddrinfo(resmulti);
if(closesocket(s) == 0)
printf("closesocket(s) is OK!\n");
else
printf("closesocket(s) failed with error code %d\n",WSAGetLastError());
if(WSACleanup() == 0)
printf("WSACleanup() is OK!\n");
else
printf("WSACleanup() failed with error code %d\n", WSAGetLastError());
return 0;
}
Next, let add the common resolve.h and its definition file, resolve.cpp into the project. Firstly, copy both files from the previous project.
------------------------------------------------------
Then paste those file into the current project file.
Then, add those files into the project using Add > Existing Item menu.
Multiple select those files and click Add button.
Those files should be visible in the solution explorer as shown in the following Figure.
Before you can run the project, make sure MSMQ is installed on the tested machine. The following screenshots show the steps on installing Message Queuing (MSMQ) from the Windows Component Wizard for Windows XP Pro. Start > Control Panel > Add or Remove Programs > Add/Remove Windows Components > Message Queuing.
You can verify the MSMQ installation through Computer Management snap-in.
Build and run the project.
Run the program as receiver/client (in this case on machine with IP 192.168.1.2 with MSMQ installed)
Run the program as sender/server (in this case on machine 192.168.1.1 with MSMQ installed).
The previous receiver/client screenshot output sample.
Useful References:
1. IPv4 Multicasting Tools and Settings.
2. Windows Server 2003 Resource Kit Tools.
3. TCP/IP and NBT configuration parameters for Windows XP.
< Multicasting & IPv6 | Multicasting Main | ATM & Multicasting >