< Multicasting & IPv6 | Multicasting Main | ATM & Multicasting >


 

 

Multicasting 9 Part 5

 

 

What do we have in this chapter 9 part 5?

  1. Reliable Multicasting (RM)

  2. Reliable Sender

  3. Modifying the Window Size

  4. Forward Error Checking (FEC)

  5. Reliable Receiver

 

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.

  1. Create a reliable multicast socket.

  2. Bind the socket to INADDR_ANY.

  3. Set the outgoing interface using RM_SET_SEND_IF.

  4. Connect the socket to the multicast group address that the data transmission is to take place on.

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.

 

Modifying the Window Size

 

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.

 

Forward Error Correcting (FEC)

 

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.

 

Reliable Receiver

 

Setting up a reliable multicast receiver consists of the following five steps.

  1. Create a reliable multicast socket.

  2. Bind the socket specifying the multicast group address to join the session on.

  3. 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.

  4. Call listen().

  5. 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.

 

Reliable multicast protocol: Creating a new empty Win32 console mode application project

 

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.

 

------------------------------------------------------

Winsock and Multicasting: Reliable multicast protocol, copying the header and its definition files.

 

Then paste those file into the current project file.

 

Winsock and Multicasting: Reliable multicast protocol, pasting the header and its definition files.

 

Then, add those files into the project using Add > Existing Item menu.

 

Winsock and Multicasting: Reliable multicast protocol, adding the header and its definition files to the existing project.

 

Multiple select those files and click Add button.

 

Winsock and Multicasting: Reliable multicast protocol, selecting the header and its definition files.

 

Those files should be visible in the solution explorer as shown in the following Figure.

 

Winsock and Multicasting: Reliable multicast protocol, the added header and its definition files seen in Solution explorer.

 

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.

 

Winsock and Multicasting: Reliable multicast protocol, adding MSMQ service to Windows XP Pro

 

 

Winsock and Multicasting: Reliable multicast protocol, selecting MSMQ HTTP Support sub component

 

 

Winsock and Multicasting: Reliable multicast protocol, wizard is configuring the MSMQ service to Windows XP Pro

 

You can verify the MSMQ installation through Computer Management snap-in.

 

Winsock and Multicasting: Reliable multicast protocol, verifying the MSMQ service using computer management

 

Build and run the project.

 

Winsock and Multicasting: Reliable multicast protocol, running the program example without any argument

 

Run the program as receiver/client (in this case on machine with IP 192.168.1.2 with MSMQ installed)

 

Winsock and Multicasting: Reliable multicast protocol, running the program example as receiver/client

 

Run the program as sender/server (in this case on machine 192.168.1.1 with MSMQ installed).

 

Winsock and Multicasting: Reliable multicast protocol, running the program example as sender or server

 

The previous receiver/client screenshot output sample.

 

Winsock and Multicasting: Reliable multicast protocol, the client/receiver screenshot when communication was completed

 

 

 

Useful References:

 

1.      IPv4 Multicasting Tools and Settings.

2.      Windows Server 2003 Resource Kit Tools.

3.      An IP multicast may not be sent correctly from Windows 2000, Windows XP, or Windows Server 2003.

4.      TCP/IP and NBT configuration parameters for Windows XP.

 


< Multicasting & IPv6 | Multicasting Main | ATM & Multicasting >