A Tutorial On IP Multicast + IBM Examples
A Tutorial On IP Multicast + IBM Examples
Introduction
This tutorial assumes basic familiarity with the socket programming abstraction found in many variants of the UNIX Operating System. This tutorial will illustrate how to use sockets to join an IP multicast group and send and receive data from multicast groups.
As the values of the TTL field increase, routers will expand the number of hops they will forward a multicast packet. To provide meaningful scope control, multicast routers enforce the following "thresholds" on forwarding based on the TTL field: 0 -- restricted to the same host 1 -- restricted to the same subnet 32 -- restricted to the same site 64 -- restricted to the same region 128 -- restricted to the same continent 255 -- unrestricted
To receive multicast packets, an application must first request that the host join a particular multicast group. This is done using another call to setsockopt(2):
struct ip_mreq mreq; setsockopt(sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
For now, imr_interface can be safely set to INADDR_ANY to join on the default multicast network interface. In addition to the host part of an IP multicast address, there is also a port number, as in TCP or UDP sockets. This port number information is used by the kernel to decide which port on the local machine to route packets to. However, unlike in TCP or UDP, there can be many sockets which receive IP multicast packets off a single local port. Binding the socket to a local port is done with bind(2), and is done in the same manner as binding to a UDP address. After the bind(2) has been performed and we have used setsockopt(2) to join a multicast group, we are ready to receive. An ordinary recvfrom(2) call may be used to read datagrams off of the receive socket.
#define HELLO_PORT 12345 #define HELLO_GROUP "225.0.0.37" void main(int argc, char *argv[]) { struct sockaddr_in addr; int fd, cnt, opt;
struct ip_mreq mreq; char *message="Hello, World!"; /* create what looks like an ordinary UDP socket */ fd=socket(AF_INET,SOCK_DGRAM,0); if ( fd < 0) { perror("socket"); exit(1); } /* set up destination address */ memset(&addr,0,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=inet_addr(HELLO_GROUP); addr.sin_port=htons(HELLO_PORT); /* now just sendto() our destination! */ while (1) { size = sizeof(addr); cnt = sizeof(message); opt = sendto(fd,message,cnt ,0,(struct sockaddr *) &addr,size ); if ( opt < 0) { perror("sendto"); exit(1); } sleep(1); } }
Listener Program
/* /* /* /* /* listener.c -- joins a multicast group and echoes all data it receives from */ the group to its stdout... */ Antony Courtney, 25/11/94 */ Modified by: Frdric Bastien (25/03/04) */ to compile without warning and work correctly */ <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> <time.h> <string.h> <stdio.h>
#define HELLO_PORT 12345 #define HELLO_GROUP "225.0.0.37" #define MSGBUFSIZE 256 main(int argc, char *argv[]) { struct sockaddr_in addr; int fd, nbytes, addrlen, size; struct ip_mreq mreq; char msgbuf[MSGBUFSIZE]; u_int yes=1; /*** MODIFICATION TO ORIGINAL */
/* create what looks like an ordinary UDP socket */ fd=socket(AF_INET,SOCK_DGRAM,0); if ( fd < 0) { perror("socket"); exit(1); }
/**** MODIFICATION TO ORIGINAL */ /* allow multiple sockets to use the same PORT number */ Opt = setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)); if ( opt < 0) { perror("Reusing ADDR failed"); exit(1); } /*** END OF MODIFICATION TO ORIGINAL */ /* set up destination address */ memset(&addr,0,sizeof(addr)); addr.sin_family=AF_INET; addr.sin_addr.s_addr=htonl(INADDR_ANY); /* N.B.: differs from sender */ addr.sin_port=htons(HELLO_PORT); /* bind to receive address */ Opt = bind(fd,(struct sockaddr *) &addr,sizeof(addr)); if ( opt < 0) { perror("bind"); exit(1); } /* use setsockopt() to request that the kernel join a multicast group */ mreq.imr_multiaddr.s_addr = inet_addr(HELLO_GROUP); mreq.imr_interface.s_addr = htonl(INADDR_ANY); opt = setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)); if ( opt < 0) { perror("setsockopt"); exit(1); } /* now just enter a read-print loop */ while (1) { addrlen=sizeof(addr); nbytes = recvfrom(fd,msgbuf,MSGBUFSIZE,0,(struct sockaddr *)&addr,&addrlen)); if ( nbytes < 0) { perror("recvfrom"); exit(1); } puts(message); } }
IP_ADD_MEMBERSHIP: Joins the multicast group specified. IP_DROP_MEMBERSHIP: Leaves the multicast group specified. IP_MULTICAST_IF: Sets the interface over which outgoing multicast datagrams are sent. IP_MULTICAST_TTL: Sets the Time To Live (TTL) in the IP header for outgoing multicast datagrams. IP_MULTICAST_LOOP: Specifies whether or not a copy of an outgoing multicast datagram is delivered to the sending host as long as it is a member of the multicast group.
Socket flow of events: Sending multicast datagrams The following sequence of the socket calls provide a description of the graphic. It also describes the relationship between two applications that send and receive multicast datagrams. Each set of flows contain links to usage notes on specific APIs. If you need more details on the use of a particular API, you can use these links. The send a multicast datagram uses the following sequence of function calls:
1. The socket() function returns a socket descriptor representing an endpoint. The statement also identifies that the INET (Internet Protocol) address family with the TCP transport (SOCK_DGRAM) will be used for this socket. This socket with send datagrams to another application. 2. The sockaddr_in structure specifies the destination IP address and port number. In this example, the address is 225.1.1.1 and the port number is 5555. 3. The setsockopt() function sets the IP_MULTICAST_LOOP socket option so that the sending system will not recieve a copy of the multicast datagrams it transmit. 4. The setsockopt() function uses the IP_MULTICAST_IF socket option which defines the local interface over which the multicast datagrams are sent. 5. The sendto() function sends multicast datagrams to the specified group IP addresses. 6. The close() function closes any open socket descriptors. Socket flow of events: Receive multicast datagrams The receive a multicast datagram uses the following sequence of function calls: 1. The socket() function returns a socket descriptor representing an endpoint. The statement also identifies that the INET (Internet Protocol) address family with the TCP transport (SOCK_DGRAM) will be used for this socket. This socket with send datagrams to another application. 2. The setsockopt() function sets the SO_REUSEADDR socket option to allow multiple applications to receive datagrams that are destined to the same local port number. 3. The bind() function specifies the local port number. In this example, the IP address is specifed as INADDR_ANY to recieve datagrams that are addressed to the multicast group. 4. The setsockopt() function uses the IP_ADD_MEMBERSHIP socket option which joins the multicast group that receives the datagrams. When joining a group, specify the class D group address along with the IP address of a local interface. The system must call the IP_ADD_MEMBERSHIP socket option for each local interface receiving the multicast datagrams. In this example, the multicast group (225.1.1.1) is joined on the local 9.5.1.1 interface. Note: IP_ADD_MEMBERSHIP option must be called for each local interface over which the multicast datagrams are to be received. 5. The read() function reads multicast datagrams that are being sent.. 6. The close() function closes any open socket descriptors.
/* Send Multicast Datagram code example. */ /* Create a datagram socket on which to send. */ sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("opening datagram socket"); exit(1); } /* Initialize the group sockaddr structure with a */ /* group address of 225.1.1.1 and port 5555. */ memset((char *) &grpSock, 0, sizeof(grpSock)); grpSock.sin_family = AF_INET; grpSock.sin_addr.s_addr = inet_addr("225.1.1.1"); grpSock.sin_port = htons(5555); /* Disable loopback so you do not receive your own datagrams. */ { char loopch=0; size = sizeof(loopch); opt = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char*)&loopch, size); if ( size < 0) { perror("setting IP_MULTICAST_LOOP:"); /* */ close(sd); exit(1); } } /* Set local interface for outbound multicast datagrams. */ /* The IP address specified must be associated with a local, */ /* multicast-capable interface. */ localInterface.s_addr = inet_addr("9.5.1.1"); size = sizeof(localInterface); opt = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, size); if ( opt < 0) { perror("setting local interface"); /* */ close(sd); exit(1); } /* Send a message to the multicast group specified by the /* groupSock sockaddr structure. */ datalen = 10; size = sizeof(grpSock); opt = sendto(sd,databuf,datalen,0,(struct sockaddr*)&grpSock, size); if ( opt < 0) perror("sending datagram message");
<sys/types.h> <sys/socket.h> <arpa/inet.h> <netinet/in.h> <stdio.h> <stdlib.h> localSock; group; sd, opt, datalen; databuf[1024];
int main (int argc, char *argv[]) { /* Receive Multicast Datagram code example. */ /* Create a datagram socket on which to receive. */ sd = socket(AF_INET, SOCK_DGRAM, 0); if (sd < 0) { perror("opening datagram socket"); exit(1); } /* Enable SO_REUSEADDR to allow multiple instances of this application to receive copies of the multicast datagrams. */ int reuse=1; opt = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) if ( opt < 0) { perror("setting SO_REUSEADDR"); /* close socket and exit() */ close(sd); exit(1); } /* Bind to the proper port number with the IP address specified as INADDR_ANY. */ memset((char *) &localSock, 0, sizeof(localSock)); localSock.sin_family = AF_INET; localSock.sin_port = htons(5555);; localSock.sin_addr.s_addr = INADDR_ANY; opt = bind(sd, (struct sockaddr*)&localSock, sizeof(localSock)); if ( opt < 0 ) { perror("binding datagram socket"); /* close socket and exit() */ close(sd); exit(1); } /* Join the multicast group 225.1.1.1 on the local 9.5.1.1 interface. */ /* Note that this IP_ADD_MEMBERSHIP option must be called for each local */ /* interface over which the multicast datagrams are to be received. */ group.imr_multiaddr.s_addr = inet_addr("225.1.1.1"); group.imr_interface.s_addr = inet_addr("9.5.1.1"); opt = setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP,(char *)&group, sizeof(group)); if ( opt < 0) { perror("adding multicast group"); close(sd); exit(1); // close socket and exit() } /* Read from the socket. */ datalen = sizeof(databuf); if (read(sd, databuf, datalen) < 0) { perror("reading datagram message"); close(sd); exit(1); //close socket and exit() } }
Socket flow of events: Server send contents of a file The following sequence of the socket calls provide a description of the graphic. It also describes the relationship between two applications that send and receive files. Each set of flows contain links to usage notes on specific APIs. If you need more details on the use of a particular API, you can use these links. The Example: Use accept_and_recv() and send_file() APIs to send contents of a file uses the following sequence of function calls: 1. The server calls socket(), bind(), and listen() to create a listening socket. 2. The server initializes the local and remote address structures. 3. The server calls accept_and_recv() to wait for an incoming connection and to wait for the first data buffer to arrive over this connection. This call returns the number of bytes that is received and the local and remote addresses that are associated with this connection. This call is a combination of the accept(), getsockname(), and recv() APIs. 4. The server calls open() to open the file whose name was obtained as data on the accept_and_recv() from the client application. 5. The memset() function is used to set all of the fields of the sf_parms structure to an initial value of 0. The server sets the file descriptor field to the value that open() returned. The server then sets the file bytes field to -1 to indicate that the server should send the entire file. The system is sending the entire file, so you do not need to assign the file offset field. 6. The server calls send_file() to transmit the contents of the file. send_file() does not complete until the entire file has been sent or an interrupt occurs. send_file() is more efficient because the application does not have to go into a read() and send() loop until the file finishes. 7. The server specifies the SF_CLOSE flag on the send_file() API. The SF_CLOSE flag informs the send_file() API that it should automatically close the socket connection when the last byte of the file and the trailer buffer (if specified) have been sent successfully. The application does not need to call close() if the SF_CLOSE flag is specified.
Socket flow of events: Client request for file The Example: Client request for a file uses the following sequence of function calls: 1. This client program takes from zero to two parameters. The first parameter (if specified) is the dotted-decimal IP address or the host name where the server application is located. The second parameter (if specified) is the name of the file that the client attempts to obtain from the server. A server application sends the contents of the specified file to the client. If the user does not specify any parameters, then the client uses INADDR_ANY for the server's IP address. If the user does not specify a second parameter, the program prompts the user to enter a file name. 2. The client calls socket() to create a socket descriptor. 3. The client calls connect() to establish a connection to the server. Step one obtained the IP address of the server. 4. The client calls send() to inform the server what file name it wants to obtain. Step one obtained the name of the file. 5. The client goes into a "do" loop calling recv() until the end of the file is reached. A return code of 0 on the recv() means that the server closed the connection. 6. The client calls close() to close the socket.
IBM -- Example: Use accept_and_recv() and send_file() APIs to send contents of a file
The following example enables a server to perform the steps listed below to communicate with a client by using the send_file() and accept_and_recv() APIs. For information on the use of code examples, see the code disclaimer.
/* Server example send file data to client */ #include #include #include #include #include #include <stdio.h> <stdlib.h> <errno.h> <fcntl.h> <sys/socket.h> <netinet/in.h>
#define SERVER_PORT 12345 void main (int argc, char *argv[]) { int i, num, rc, flag = 1; int fd, listen_sd, accept_sd = -1; size_t local_addr_length; size_t remote_addr_length; size_t total_sent; struct struct struct struct char sockaddr_in sockaddr_in sockaddr_in sf_parms buffer[255]; addr; local_addr; remote_addr; parms;
/* If an argument is specified, use it to control the number of incoming connections */ if (argc >= 2) num = atoi(argv[1]);
else num = 1; /* Create an AF_INET stream socket to receive incoming connections on */ listen_sd = socket(AF_INET, SOCK_STREAM, 0); if (listen_sd < 0) { perror("socket() failed"); exit(-1); } /* Set the SO_REUSEADDR bit so that you do not have to wait 2 minutes before restarting the server */ rc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *)&flag, sizeof(flag)); if (rc < 0){ perror("setsockop() failed"); close(listen_sd); exit(-1); } /* Bind the socket */ memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(SERVER_PORT); rc = bind(listen_sd,(struct sockaddr *)&addr, sizeof(addr)); if (rc < 0) { perror("bind() failed"); close(listen_sd); exit(-1); } /* Set the listen backlog */ rc = listen(listen_sd, 5); if (rc < 0) { perror("listen() failed"); close(listen_sd); exit(-1); } /* Initialize the local and remote addr lengths */ local_addr_length = sizeof(local_addr); remote_addr_length = sizeof(remote_addr); /* Inform the user that the server is ready */ printf("The server is ready\n"); /* Go through the loop once for each connection */ for (i=0; i < num; i++) { /* Wait for an incoming connection */ printf("Iteration: %d\n", i+1); printf(" waiting on accept_and_recv()\n"); rc = accept_and_recv(listen_sd, &accept_sd, (struct sockaddr *)&remote_addr, &remote_addr_length, (struct sockaddr *)&local_addr, &local_addr_length, &buffer, sizeof(buffer)); if (rc < 0) { perror("accept_and_recv() failed"); close(listen_sd); close(accept_sd); exit(-1); } printf(" Request for file: %s\n", buffer); /* Open the file to retrieve */ fd = open(buffer, O_RDONLY); if (fd < 0) { perror("open() failed"); close(listen_sd); close(accept_sd); exit(-1); }
/* Initialize the sf_parms structure */ memset(&parms, 0, sizeof(parms)); parms.file_descriptor = fd; parms.file_bytes = -1; /* Initialize the counter of the total number of bytes sent */ total_sent = 0; /* Loop until the entire file has been sent */ do { rc = send_file(&accept_sd, &parms, SF_CLOSE); if (rc < 0) { perror("send_file() failed"); close(fd); close(listen_sd); close(accept_sd); exit(-1); } total_sent += parms.bytes_sent; } while (rc == 1); printf(" Total number of bytes sent: %d\n", total_sent);
/* Close the listen socket */ close(listen_sd); /* Close the accept socket */ if (accept_sd != -1) close(accept_sd); }
addr.sin_family = AF_INET; addr.sin_port = htons(SERVER_PORT); /* Determine the host name and IP address of the */ /* machine the server is running on */ if (argc < 2) addr.sin_addr.s_addr = htonl(INADDR_ANY); else if (isdigit(*argv[1])) addr.sin_addr.s_addr = inet_addr(argv[1]); else { host_ent = gethostbyname(argv[1]); if (host_ent == NULL) { printf("Host not found!\n"); exit(-1); } memcpy((char *)&addr.sin_addr.s_addr, host_ent->h_addr_list[0], host_ent->h_length); } /* Check to see if the user specified a file name on the command line */ if (argc == 3) strcpy(filename, argv[2]); else { printf("Enter the name of the file:\n"); gets(filename); } /* Create an AF_INET stream socket */ sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0){ perror("socket() failed"); exit(-1); } printf("Socket completed.\n"); /* Connect to the server */ rc = connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)); if (rc < 0){ perror("connect() failed"); close(sockfd); exit(-1); } printf("Connect completed.\n"); /* Send the request over to the server */ rc = send(sockfd, filename, strlen(filename) + 1, 0); if (rc < 0) { perror("send() failed"); close(sockfd); exit(-1); } printf("Request for %s sent\n", filename); /* Receive the file from the server */ do { rc = recv(sockfd, buffer, sizeof(buffer), 0); if (rc < 0) { perror("recv() failed"); close(sockfd); exit(-1); } else if (rc == 0) { printf("End of file\n"); break; } printf("%d bytes received\n", rc); } while (rc > 0); /* Close the socket */ close(sockfd); }