< C++ & C# Raw Socket IGMP Program Examples | Main | C++ Multicast Program Example (Cont...) >

 


 

Chapter 8 Part 23:

Client (and Server) Sockets Communication

 

 

What do we have in this chapter 8 Part 23?

  1. Setting a Socket to Non-Blocking Mode

  2. IP Multicasting

  3. Joining a Group

  4. Sending Data to a Multicast Group

  5. Leaving a Group

  6. C++ Multicast Program Example

 

 

Setting a Socket to Non-Blocking Mode

 

If you are familiar with the Winsock API, you know that the most common ioctl is the Winsock define FIONBIO, which is used to change a socket from blocking to non-blocking mode. All code samples in this chapter so far have shown blocking sockets. When a socket is put into the non-blocking mode, the operation will throw an exception if it can’t be completed immediately. In the .NET Framework, however, the FIONBIO ioctl is not exposed through the IOControl method but as a property of the Socket class called Blocking.

Non-blocking sockets should be avoided if possible because they’re inefficient and prone to logic errors. The non-blocking mode is a carryover from BSD Sockets and Winsock 1.1, where it was the only alternative to blocking sockets; there were no asynchronous operations. Setting a socket to non-blocking mode allowed the application to service multiple sockets at once with the drawback of requiring the application to poll for events (such as read and write) on the socket. The Socket class exposes a Poll() method that does this. However, polling for events leads to inefficient network usage because the application must spend time querying each socket for certain events. It’s much simpler and more efficient to use asynchronous pattern method operations instead, as described in Chapter 3.

 

IP Multicasting

 

Earlier in the chapter, we introduced the concept of multicasting data, a method of delivering data from one source to many receivers that’s accomplished by each receiver “joining” a particular multicast address. All receivers that are interested in the same traffic join the same multicast group. Both the IPv4 and IPv6 address spaces reserve a portion for multicast addresses.

A multicast socket is simply a UDP socket that uses the SocketOptionName.AddMembership and SocketOptionName.DropMembership socket options to join one or more groups. Both IPv4 and IPv6 support multicasting, so the option levels specified are SocketOptionLevel.IP and SocketOptionLevel.IPv6, respectively.

 

Joining a Group

 

To join a multicast group, the UDP socket must be bound to a local address and port. When joining a group, the multicast address and the local interface on which to join the group are parameters to the SetSocketOption call, which means that it’s possible to join one or more multicast groups on one or more local interfaces all on the same socket. Therefore, the UDP socket should be bound to the wildcard address.

 

When joining an IPv4 multicast group, a MulticastOption object must be created that identifies the multicast group IPAddress and the local interface IPAddress to be joined. The following code illustrates IPv4 and multicasting:

 

C#

 

Socket           mcastSocket;

IPEndPoint       localAddress = new IPEndPoint( IPAddress.Any, 5150 );

MulticastOption  mcastOption;

 

try

{

    mcastOption = new MulticastOption(

        IPAddress.Parse("234.6.7.8"),

        IPAddress.Parse("10.10.10.1")

        );

    mcastSocket = new Socket(

        localAddress.AddressFamily,

        SocketType.Dgram,

        ProtocolType.Udp

        );

    mcastSocket.Bind( localAddress );

    mcastSocket.SetSocketOption(

        SocketOptionLevel.IP,

        SocketOptionName.AddMembership,

        mcastOption

        );

}

catch( SocketException err )

{

    Console.WriteLine("Error: {0}", err.Message);

}

 

Visual Basic .NET

 

Dim mcastSocket As Socket

Dim localAddress As IPEndPoint = New IPEndPoint(IPAddress.Any, 5150)

Dim mcastOption As MulticastOption

 

Try

    mcastOption = New MulticastOption( _

        IPAddress.Parse("234.6.7.8"), _

        IPAddress.Parse("10.10.10.1") _

        )

    mcastSocket = New Socket( _

        localAddress.AddressFamily, _

        SocketType.Dgram, _

        ProtocolType.Udp _

        )

    mcastSocket.Bind( localAddress )

    mcastSocket.SetSocketOption( _

        SocketOptionLevel.IP, _

        SocketOptionName.AddMembership, _

        mcastOption _

        )

Catch err As SocketException

    Console.WriteLine("Error: {0}", err.Message)

End Try

 

This code creates an IPv4/UDP socket, binds it to the wildcard address and port 5150, and joins the group 234.6.7.8 on the local interface 10.10.10.1. If no such local interface exists, a SocketException is thrown. Once joined, the socket can receive data via the ReceiveFrom() method for multicast data sent to the group 234.5.6.8 and port 5150.

Joining multicast groups on an IPv6 socket is slightly different in that it takes a different structure to join the group on an interface. Instead of a MulticastOption object, an IPv6MulticastOption is required, which is constructed with the IPv6 multicast group’s IPAddress and the ScopeId of the local interface’s IPAddress object. Because IP addresses tend to be transient, IPv6 multicasting allows only joining groups given the interface’s index, which never changes (unless the interface is disabled or removed or the machine is rebooted). The following code shows how to create an IPv6 socket and join a multicast group:

 

C#

 

Socket      mcastSocket;

IPEndPoint  localAddress = new IPEndPoint( IPAddress.IPv6Any, 5150 );

IPv6MulticastOption  mcastOption;

 

try

{

    mcastOption = new IPv6MulticastOption(

        IPAddress.Parse( "ff12::1" ),

        IPAddress.Parse( "::%5" ).ScopeId

        );

    mcastSocket = new Socket(

        localAddress.AddressFamily,

        SocketType.Dgram,

        ProtocolType.Udp

        );

    mcastSocket.Bind( localAddress );

    mcastSocket.SetSocketOption(

        SocketOptionLevel.IPv6,

        SocketOptionName.AddMembership,

        mcastOption

        );

}

catch ( SocketException err )

{

    Console.WriteLine("Error: {0}", err.ToString());

}

 

Visual Basic .NET

 

Dim mcastSocket As Socket

Dim localAddress As IPEndPoint = New IPEndPoint(IPAddress.IPv6Any, 5150)

Dim mcastOption As IPv6MulticastOption

 

Try

    mcastOption = New IPv6MulticastOption( _

        IPAddress.Parse("ff12::1"), _

        IPAddress.Parse("::%5").ScopeId _

        )

    mcastSocket = New Socket( _

        localAddress.AddressFamily, _

        SocketType.Dgram, _

        ProtocolType.Udp _

        )

    mcastSocket.Bind(localAddress)

    mcastSocket.SetSocketOption( _

        SocketOptionLevel.IPv6, _

        SocketOptionName.AddMembership, _

        mcastOption _

        )

Catch err As SocketException

    Console.WriteLine("Error: {0}", err.ToString() )

End Try

 

Notice that when the IPv6MulticastOption is created, the string “::%5” is parsed into an IPAddress and the ScopeId is passed to the object’s constructor, but we could have simply specified the long value 5. Attempting to parse the string “%5” results in a FormatException because the string is not a valid IPv6 address.

Because multicasting is a one-to-many communication, it’s possible that multiple sockets on the same computer will want to receive data from the same multicast group and port. To do so, each socket will have to bind to the same port, which is not allowed by default. In this case, each socket calls SetSocketOption with SocketOptionName.ReuseAddress, as described in Table 8-4. This call will allow multiple sockets to bind to the same address and port, and any multicast data received on that address and port will be delivered to each multicast socket bound to it. For more information on the ReuseAddress option and possible security implications, see Chapter 9.

 

Sending Data to a Multicast Group

 

Receiving data sent to a multicast group is the same as with non-multicast sockets, but sending data to a multicast group has a catch. Because any computer can join a group on any interface, the network stack has no clue as to which local interface multicast data should be sent from. With non-multicast traffic, the local interface to originate traffic from is determined by the routing table. With multicast traffic, this does not work because multicast recipients might be present on any or all of the local computer’s networks if the local computer has multiple network interfaces. (A computer with multiple network interfaces is multihomed.) By default, the network stack will send the multicast traffic on the first interface in the routing table unless specified otherwise.

An application can specify the local interface that multicast traffic should be sent from on a socket by using SetSocketOption and the SocketOptionName.MulticastInterface. The option level is IP or IPv6, depending on which protocol is being used. The option value is the IPv4 IPAddress object or the ScopeId field of the IPv6 IPAddress that identifies the local interface that multicast data should be sent from.

Note that to send data to a multicast group, it’s not required for the socket to join the group it’s sending to or for it to join any group at all. Also note that the default time-to-live (TTL) value for sent multicast traffic is one, which means that any data sent will not travel beyond the first router on the network. However, the SocketOptionName.MulticastTimeToLive socket option can change the default TTL.

 

Leaving a Group

 

Membership to a multicast group can be dropped by following the same steps for joining except specifying SocketOptionName.DropMembership instead of SocketOptionName.AddMembership. This ability to drop membership is useful if the multicast groups that the socket is interested in change over time, such as an application that receives various channels of streaming media and each media channel is sent to a separate multicast group. Otherwise, if the socket is closed with the Close() method, all multicast groups joined will be dropped implicitly.

 

C++ Multicast Program Example

 

Create a new CLR console application project and you might use MulticastCP as the name.

 

C++ Multicast Program Example - creating a new CLR console application project in VS 2008 IDE

 

Add/edit the following using directives at the top of the file.

 

using namespace System;

using namespace System;

using namespace System::Collections;

using namespace System::Net;

using namespace System::Net::Sockets;

 

Add the following MulticastEndpoint class and its code.

 

 

 

public ref class MulticastEndpoint

{

public:

            ArrayList^       localInterfaceList;

            ArrayList^       multicastJoinList;                                           

            Socket^           mcastSocket;

            IPAddress^        bindAddress;

            int              bufferSize;

            int              localPort;   

            array<Byte>^     dataBuffer;

 

            /// <summary>

            /// A simple constructor

            /// </summary>

            public:

            MulticastEndpoint()

            {

               localInterfaceList = gcnew ArrayList();

               multicastJoinList = gcnew ArrayList();

               bufferSize = 512;

               mcastSocket = nullptr;

            }

 

            /// <summary>

            /// This method creates the socket, joins it to the given multicast groups, and initializes

            /// the send/receive buffer. Note that the local bind address should be the wildcard address

            /// because it is possible to join multicast groups on one or more local interface and if

            /// a socket is bound to an explicit local interface, it can lead to user confusion (although

            /// this does currently work on Windows OSes).

            /// </summary>

            /// <param name="port">Local port to bind socket to</param>

            /// <param name="bufferLength">Length of the send/recv buffer to create</param>

public:

            void Create(int port, int bufferLength)

            {

                        localPort = port;

 

                        Console::WriteLine("Creating socket, joining multicast group and");

                        Console::WriteLine("     initializing the send/receive buffer");

                        try

                        {

                                    // If no bind address was specified, pick an appropriate one based on the multicast

                                    // group being joined.

                                    if ( bindAddress == nullptr )

                                    {

                                                IPAddress^ tmpAddr = (IPAddress^) multicastJoinList[0];

 

                                                if ( tmpAddr->AddressFamily == AddressFamily::InterNetwork )

                                                   bindAddress = IPAddress::Any;

                                                else if ( tmpAddr->AddressFamily == AddressFamily::InterNetworkV6 )

                                                   bindAddress = IPAddress::IPv6Any;

                                    }

 

                                    // Create the UDP socket

                                    Console::WriteLine("Creating the UDP socket...");

                                    mcastSocket = gcnew Socket(

                                                bindAddress->AddressFamily,

                                                SocketType::Dgram,

                                                (ProtocolType)0

                                                );

 

                                    Console::WriteLine("{0} (IPv4 or IPv6) multicast socket created...", bindAddress->AddressFamily);

 

                                    // Bind the socket to the local endpoint

                                    Console::WriteLine("Binding the socket to the local endpoint...");

                                    IPEndPoint^  bindEndPoint = gcnew IPEndPoint( bindAddress, port );

                                    mcastSocket->Bind( bindEndPoint );

                                    Console::WriteLine("Multicast socket bound to: {0}", bindEndPoint->ToString());

 

                                    // Join the multicast group

                                    Console::WriteLine("Joining the multicast group...");

                                    for(int i = 0; i < multicastJoinList->Count ;i++)

                                    {

                                                for(int j = 0; j < localInterfaceList->Count ;j++)

                                                {

                                                            // Create the MulticastOption structure which is required to join the

                                                            //    multicast group

                                                            if ( mcastSocket->AddressFamily == AddressFamily::InterNetwork )

                                                            {

                                                                        MulticastOption^     mcastOption = gcnew MulticastOption(

                                                                                    (IPAddress^) multicastJoinList[i],

                                                                                    (IPAddress^) localInterfaceList[j]

                                                                                    );

 

                                                                        mcastSocket->SetSocketOption(

                                                                                    SocketOptionLevel::IP,

                                                                                    SocketOptionName::AddMembership,

                                                                                    mcastOption

                                                                                    );

                                                            }

                                                            else if ( mcastSocket->AddressFamily == AddressFamily::InterNetworkV6 )

                                                            {

                                                                        IPv6MulticastOption^ ipv6McastOption = gcnew IPv6MulticastOption(

                                                                                    (IPAddress^) multicastJoinList[i], Convert::ToInt64(localInterfaceList[j]));

 

                                                                        mcastSocket->SetSocketOption(

                                                                                    SocketOptionLevel::IPv6,

                                                                                    SocketOptionName::AddMembership,

                                                                                    ipv6McastOption

                                                                                    );

                                                            }

 

                                                            Console::WriteLine("Joined multicast group {0} on interface {1}",

                                                                        multicastJoinList[i]->ToString(),

                                                                        localInterfaceList[j]->ToString()

                                                                        );

                                                }

                                    }

 

                                    // Allocate the send and receive buffer

                                    Console::WriteLine("Allocating the send and receive buffer...");

                                    dataBuffer = gcnew array<Byte>( bufferLength );

                        }

                        catch(SocketException^ err)

                        {

                                    Console::WriteLine("Exception occurred when creating multicast socket: {0}", err->Message);

                        }

            }

 

            /// <summary>

            /// This method drops membership to any joined groups. To do so, you have to

            /// drop the group exactly as you joined it -- that is the local interface

            /// and multicast group must be the same as when it was joined. Also note

            /// that it is not required to drop joined groups before closing a socket.

            /// When a socket is closed all multicast joins are dropped for you -- this

            /// routine just illustrates how to drop a group if you need to in the middle

            /// of the lifetime of a socket.

            /// </summary>

            public:

            void LeaveGroups()

            {

                        try

                        {

                                    Console::WriteLine("Dropping membership to any joined groups...");

                                    for(int i = 0; i < multicastJoinList->Count ;i++)

                                    {

                                                for(int j = 0; j < localInterfaceList->Count ;j++)

                                                {

                                                            // Create the MulticastOption structure which is required to drop the

                                                            //    multicast group (the same structure used to join the group is

                                                            //    required to drop it).

                                                            if ( mcastSocket->AddressFamily == AddressFamily::InterNetwork )

                                                            {

                                                                        MulticastOption^     mcastOption = gcnew MulticastOption(

                                                                                    (IPAddress^) multicastJoinList[i], (IPAddress^) localInterfaceList[j]);

 

                                                                        mcastSocket->SetSocketOption(

                                                                                    SocketOptionLevel::IP,

                                                                                    SocketOptionName::DropMembership,

                                                                                    mcastOption

                                                                                    );

                                                            }

                                                            else if ( mcastSocket->AddressFamily == AddressFamily::InterNetworkV6 )

                                                            {

                                                                        IPv6MulticastOption^ ipv6McastOption = gcnew IPv6MulticastOption(

                                                                                    (IPAddress^) multicastJoinList[i],

                                                                                    ( (IPAddress^) localInterfaceList[j] )->ScopeId

                                                                                    );

 

                                                                        mcastSocket->SetSocketOption(

                                                                                    SocketOptionLevel::IPv6,

                                                                                    SocketOptionName::DropMembership,

                                                                                    ipv6McastOption

                                                                                    );

                                                            }

 

                                                            Console::WriteLine("Dropping multicast group {0} on interface {1}",

                                                                        multicastJoinList[i]->ToString(),

                                                                        localInterfaceList[j]->ToString()

                                                                        );

                                                }

                                    }

                        }

                        catch(Exception^ err)

                        {

                                    Console::WriteLine("LeaveGroups: No multicast groups joined: " + err->Message);

                        }

            }

 

            /// <summary>

            /// This method sets the outgoing interface when a socket sends data to a multicast

            /// group. Because multicast addresses are not routable, the network stack simply

            /// picks the first interface in the routing table with a multicast route. In order

            /// to change this behavior, the MulticastInterface option can be used to set the

            /// local interface on which all outgoing multicast traffic is to be sent (for this

            /// socket only). This is done by converting the 4 byte IPv4 address (or 16 byte

            /// IPv6 address) into a byte array.

            /// </summary>

            /// <param name="sendInterface"></param>

            public:

            void SetSendInterface( IPAddress^ sendInterface )

            {

                        // Set the outgoing multicast interface

                        try

                        {

                                    Console::WriteLine("Setting the outgoing multicast interface...");

                                    if ( mcastSocket->AddressFamily == AddressFamily::InterNetwork )

                                    {

                                                mcastSocket->SetSocketOption(

                                                            SocketOptionLevel::IP,

                                                            SocketOptionName::MulticastInterface,

                                                            sendInterface->GetAddressBytes()

                                                            );

                                    }

                                    else

                                    {

                                                array<Byte>^ interfaceArray = BitConverter::GetBytes( (int)sendInterface->ScopeId );

 

                                                mcastSocket->SetSocketOption(

                                                            SocketOptionLevel::IPv6,

                                                            SocketOptionName::MulticastInterface,

                                                            interfaceArray

                                                            );

                                    }

                                    Console::WriteLine("Setting multicast send interface to: " + sendInterface->ToString());

                        }

                        catch ( SocketException^ err )

                        {

                                    Console::WriteLine("SetSendInterface: Unable to set the multicast interface: {0}", err->Message);

                                    throw;

                        }

            }

 

            /// <summary>

            /// This method takes a string and repeatedly copies it into the send buffer

            /// to the length of the send buffer.

            /// </summary>

            /// <param name="message">String to copy into send buffer</param>

            public:

            void FormatBuffer( String^ message )

            {

                        array<Byte>^  byteMessage = Text::Encoding::ASCII->GetBytes( message );

                        int         index = 0;

 

                        // First convert the string to bytes and then copy into send buffer

                        Console::WriteLine("Formatting the string & copy to send buffer...");

                        while ( index < dataBuffer->Length )

                        {

                                    for(int j=0; j < byteMessage->Length ;j++)

                                    {

                                                dataBuffer[ index ] = byteMessage[ j ];

                                                index++;

 

                                                // Make sure we don't go past the send buffer length

                                                if ( index >= dataBuffer->Length )

                                                {

                                                    break;

                                                }

                                    }

                        }

            }

};

 

 

 


 

< C++ & C# Raw Socket IGMP Program Examples | Main | C++ Multicast Program Example (Cont...) >