Datagrams

Datagrams have the following characteristics:

  1. Creating a socket with type = SOCK_DGRAM instead of SOCK_STREAM will select the protocol UDP/IP instead of TCP/IP. UDP stands for User Datagram Protocol .
  2. No guarantee of delivery. Packets are considered best attempt delivery. There is no acknowledgment associated with UDP as there is with TCP.
  3. UDP packets are not considered a stream of bytes like TCP. If you send a block of data in TCP/IP, it is broken up into multiple packets to fit the various network buffering constraints. For example, Ethernet packets are less than 2k. In TCP/IP your programs are ignorant of packet boundaries. In UDP, buffers are not broken up into multiple packets. If the buffer is too big, the data is truncated.
  4. There is no connection associated with a conversation between a client and a server. Each packet is a one time deal. Once sent, there is no need to maintain any relationship with the recipient of the data.
  5. For each call to send a packet, we need to include an endpoint address(my_addr) which contains an IP address and port number. The routine to send a packet looks like:
    sendto(sock, buff, len, flags, my_addr, my_addr_len);
  6. For each returned packet comes back with the address of the sender.
    recvfrom (sock, buff, buffsize, flags, client_addr, client_len);
  7. The client side isn't as easy as it was in TCP/IP when we could just call connect to do all of the work. The Client must create a port number and and address structure that gets bound in with his socket. We have seen the Server do this, but now that we can't use the connect call, we have do all of this dirty work when we work with datagrams.
  8. In the following example, we are demonstrating a new feature. We can ask the system to pick our port numbers for us. The system will pick some unused port and we can find out about it with a getsockopt call.

dgram_server.c

#include <sys/socket.h> 
#include <netdb.h> 
#include <netinet/in.h>
#include <arpa/inet.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
#include <errno.h>
#define BUFSIZE 8000

main(int argc, char * argv[])
{
    int  sock, nread, rc, server_len, client_len;
    struct sockaddr_in server, client;
    char buff[BUFSIZE];
    
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        perror("SERVER socket ");
        exit(1);
    }

// Let the System to fill in the Port number this time
// This can be done with connection oriented sockets as well.
 
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    if (argc <= 1)
        server.sin_port  = htons(0);  // pick any free port
      else
        {
           server.sin_port = htons(atoi(argv[1]));
        }
    if (bind(sock, (struct sockaddr *) &server, sizeof(server)) < 0)
    {
        perror("SERVER bind ");
        close(sock);
        exit(2);
    }
    server_len = sizeof(server);
    // We call getsockname so we can look at what port number
    // the system actually picked. 
    if (getsockname(sock, (struct sockaddr *)&server, &server_len) < 0)
    {
        perror("SERVER getsockname ");
        close(sock);
        exit(3);
    }
    printf("Server using port %d\n", ntohs(server.sin_port));
    
    for (;;)
    {
        client_len = sizeof(client);
        memset(buff, 0, BUFSIZE);
        
        // recvfrom returns the address of who sent the packet
        // in the client structure
        nread = recvfrom (sock, buff, BUFSIZE, 0, 
                    (struct sockaddr *)&client, &client_len); 
        if (nread < 0)
        {
            perror("SERVER recvfrom ");
            close(sock);
            exit(4);
        }  
        buff[nread] = 0;
        printf("received: %s\n", buff);
        
        // We use the client structure to know who to send the
        // response to. 

        rc = sendto(sock, buff, strlen(buff), 0, 
                (struct sockaddr *)&client, client_len);
        if (rc < 0)
        {
           perror("SERVER sendto ");
           close(sock);
           exit(5);
        }
    
    } // end of for loop
}

dgram_client.c

#include <sys/socket.h> 
#include <netdb.h> 
#include <netinet/in.h>
#include <arpa/inet.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
#include <errno.h>
#define BUFSIZE 8000

main(int argc, char * argv[])
{
    int  sock, nread, rc, server_len, client_len;
    int  port;
    struct sockaddr_in server, client;
    char buff[BUFSIZE];
    char hostname[64]; 
    char *str; 
    struct hostent *hp;

    if (argc < 2)
    {
        printf("Useage: %s port [hostname]", argv[0]);
        exit(0);
    }
    port = atoi(argv[1]);
    gethostname(hostname, sizeof(hostname));
    if (argc >= 3)
        strcpy(hostname, argv[2]);
    hp = gethostbyname(hostname); 
    if (hp == NULL) 
    { 
        printf("\n%s: unknown host.\n", hostname); 
        exit(1); 
    } 
    
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        perror("CLIENT socket ");
        exit(1);
    }
    printf("sock = %d\n",sock);
    
    server.sin_family = AF_INET;
    memcpy(&server.sin_addr, hp->h_addr, hp->h_length); 
    server.sin_port  = htons(port);  
       
    // Note that we need to create a client address.
    // We didn't need to do this for connection oriented sockets
    // The connect statement did all of this stuff for us automatically
 
    client.sin_family = AF_INET;
    client.sin_addr.s_addr = htonl(INADDR_ANY);
    client.sin_port = htons(0);  // pick any free port

    // Note that we need to bind the client address to the socket
    // similar to what we have always done for servers.
    // Again, this detail was buried in the connect command for
    // connection oriented socket programming.

    if (bind(sock, (struct sockaddr *) &client, sizeof(client)) < 0)
    {
        perror("CLIENT bind ");
        close(sock);
        exit(2);
    }    
    
    for (;;)
    {
        printf("Enter buffer to send\n");
        str = fgets(buff, sizeof(buff), stdin);
        if (str == NULL) break; // shouldn't happen
        server_len = sizeof(server);

        rc = sendto(sock, buff, strlen(buff), 0, 
                (struct sockaddr *)&server, server_len);
        if (rc < 0)
        {
           perror("SERVER sendto ");
           close(sock);
           exit(5);
        }
        
        client_len = sizeof(client);
        memset(buff, 0, BUFSIZE);
        printf("msg sent and now waiting on recvfrom\n");
        nread = recvfrom (sock, buff, BUFSIZE, 0, 
                    (struct sockaddr *)&client, &client_len); 
        if (nread < 0)
        {
            perror("CLIENT recvfrom ");
            close(sock);
            exit(4);
        }  
        buff[nread] = 0;
        printf("received: %s\n", buff); 
    
    } // end of for loop
}


Broadcasting

  1. I didn't get my broadcasting example to work. It seems to work to broadcast from gettysburg to itself, but shrek doesn't respond to the broadcast even from itself. I should be able to startup dgram_server with the same port number on multiple machines, and each should respond to a broadcast that matches.
  2. However, I did want to pass along some information:
  3. Broadcasting can only be done with Datagrams --- TCP/IP can't by its design work in a broadcast environment.
  4. There is a socket option that needs to be set to enable broadcasting.
  5. The IP-address needs to modified to indicate the range of addresses. In my example, I make the last byte of the IP-address into FF to indicate any addresses that contain the first 3 IP-address bytes are OK.
#include <sys/socket.h> 
#include <netdb.h> 
#include <netinet/in.h>
#include <arpa/inet.h> 
#include <stdio.h> 
#include <unistd.h> 
#include <string.h> 
#include <errno.h>
#define BUFSIZE 8000

main(int argc, char * argv[])
{
    int  sock, nread, rc, server_len, client_len;
    int  port;
    struct sockaddr_in server, client;
    char buff[BUFSIZE];
    char hostname[64]; 
    char *str; 
    struct hostent *hp;
    struct in_addr HostAddr;
    int nOpt = 1;

    if (argc < 2)
    {
        printf("Useage: %s port [hostname]", argv[0]);
        exit(0);
    }
    port = atoi(argv[1]);
    gethostname(hostname, sizeof(hostname));
    if (argc >= 3)
        strcpy(hostname, argv[2]);
    hp = gethostbyname(hostname); 
    if (hp == NULL) 
    { 
        printf("\n%s: unknown host.\n", hostname); 
        exit(1); 
    } 
    
    if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0){
        perror("CLIENT socket ");
        exit(1);
    }
    printf("sock = %d\n",sock);
    setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &nOpt, sizeof(int));
       
    HostAddr = *((struct in_addr *) *(hp->h_addr_list));
    HostAddr.s_addr |= 0xff000000; // Least significant byte for Intel CPU
                                   // Some architectures require 0x000000ff
    printf("Broadcast address = %s\n", inet_ntoa(HostAddr));


    server.sin_family = AF_INET;
   // memcpy(&server.sin_addr, hp->h_addr, hp->h_length); 
    server.sin_addr = HostAddr;
    server.sin_port  = htons(port);  
       
    // Note that we need to create a client address.
    // We didn't need to do this for connection oriented sockets
    // The connect statement did all of this stuff for us automatically
 
    client.sin_family = AF_INET;
    client.sin_addr.s_addr = htonl(INADDR_ANY);
    client.sin_port = htons(0);  // pick any free port

    // Note that we need to bind the client address to the socket
    // similar to what we have always done for servers.
    // Again, this detail was buried in the connect command for
    // connection oriented socket programming.

    if (bind(sock, (struct sockaddr *) &client, sizeof(client)) < 0)
    {
        perror("CLIENT bind ");
        close(sock);
        exit(2);
    }    
    
    for (;;)
    {
        printf("Enter buffer to send\n");
        str = fgets(buff, sizeof(buff), stdin);
        if (str == NULL) break; // shouldn't happen
        server_len = sizeof(server);

        rc = sendto(sock, buff, strlen(buff), 0, 
                (struct sockaddr *)&server, server_len);
        if (rc < 0)
        {
           perror("SERVER sendto ");
           close(sock);
           exit(5);
        }
        
        client_len = sizeof(client);
        memset(buff, 0, BUFSIZE);
        printf("msg sent and now waiting on recvfrom\n");
        nread = recvfrom (sock, buff, BUFSIZE, 0, 
                    (struct sockaddr *)&client, &client_len); 
        if (nread < 0)
        {
            perror("CLIENT recvfrom ");
            close(sock);
            exit(4);
        }  
        buff[nread] = 0;
        printf("received: %s\n", buff); 
    
    } // end of for loop
}