User Tools

Site Tools


tcp_udp

This is an old revision of the document!


TCP & UDP Communications

Author: Dylan Wallace Email: wallad3@unlv.nevada.edu
Date: Last modified on 04/30/17
Keywords: TCP/IP, UDP, Communications, Server, Client

tcp_vs_udp.jpg
The photo above depicts the differences between TCP & UDP connections which allow you to create different forms of communications. The big picture problem is high-latency or low-bandwidth telerobotics. Solving this partially or completely is important because it will allow remote operation of space robotics, disaster relief, etc. This tutorial shows you how to create a TCP client & server, and a UDP talker & listener, and takes approximately 2 hours to complete.

Motivation and Audience

This tutorial's motivation is to teach the basics of TCP & UDP communications. Readers of this tutorial assumes the reader has the following background and interests:

* Know how to program in C++
* Perhaps also know how to use the Linux command line
* Perhaps additional background needed may include TCP/IP protocols
* This tutorial may also attract readers who are interested in web programming


The rest of this tutorial is presented as follows:

Protocol Introductions

For a detailed overview of TCP & UDP communications, please see Beej's Guide to Network Programming. We will go over the concepts briefly in this section.

Internet vs Intranet

Networking is a term that describes connecting multiple computers together into a “network”, allowing them to communicate with each other. Networks can be local, or they can be global, like the Internet. A purely local network, not connected to the outside world, is called an intranet. This is typically a network that is very secure, and will only want to allow communications with computers that it trusts. The Internet (note the capital I) is the interconnection of all computers across the world. This network is not necessarily centralized, but is cascaded and “netted” between many different computers, allowing them all to communicate with each other by communicating through each other, or things like routers and switches. It is also possible to have individual internets (note the lowercase i), which are typically connected to the Internet as a whole, but also have local, more secure connections to each other. These are very common in modern businesses that utilize a network infrastructure. All of these types of networks utilize the TCP/IP protocols, which we will detail below.

IP Addresses

If you use the internet at all, you have probably heard of the term IP address. IP addresses are simply a number that describes a computer connected to the Internet. IP stands for Internet Protocol.

IP addresses have two forms:

  • IPv4: the older, more common protocol; has much less IP addresses available
  • IPv6: the modern, increasingly common protocol; has many many more addresses available

IPv4 was the first worldwide Internet protocol, and is still the most common protocol used today, due to its simplicity. IPv4 uses 4 unique bytes to describe an IP address, each with a range of 0-255, giving a total address of 32 bits. This amounts to around 4.3 billion IP addresses globally, which is not enough for the growing amount of computers in the modern world. This is what led to the development of IPv6 in the late 1990's.

IPv6 is becoming more and more popular, due to its enormous amount of IP addresses available. IPv6 uses 8 unique 16-bit sections, totalling a 128-bit address. This is nearly 3.4 x 10^38 unique addresses, more than enough for every atom on Earth to have its own IP address. This has essentially solved the IP address problem for the foreseeable future.

IPv4 example: 192.168.0.225
IPv6 example: 2001:db8:c9d2:12::51 (leading zeroes can be omitted)

Sockets

Sockets are the way that we create and utilize connections when programming communications. There are two types of sockets: datagram (UDP) and stream (TCP). Sockets are tied to individual ports, which are virtual sections of a computer that are dedicated to individual processes. This allows multiple applications to communicate with each other and other networks at the same time. Ports are 16-bit numbers, giving an average computer around 65,000 of them. However, the first 1,000 are reserved for important processes.

The picture above shows the concept of data encapsulation. In computer networks, there are many different protocols to deal with. In order to make the job easier on the program, we simplify the process by “encapsulating” our data into layers of protocols. We utilize headers to tell what kind and how much data is encapsulated within that layer. In general, computers follow the OSI network model for encapsulation:

1. Application
2. Presentation
3. Session
4. Transport
5. Network
6. Data Link
7. Physical

Where the application is what is directly using the data, such as TFTP, and the physical is the last line of sending/receiving from the computer, such as Ethernet. As a programmer, we do not need to worry about all of these protocols, thanks to encapsulation, because there are services on the computer's OS that deal with the lower-level headers. That means that we only have to deal with the headers that we care about. In an application programmers case, this may be TFTP or VOIP. In our case, this is the Transport/Network layer, or TCP/IP and UDP.

TCP

TCP stands for Transmission Control Protocol, and describes a connection that send a constant stream of bytes. TCP establishes a direct connection between the two computers, and send the bytes over this direct connection. The sending computer is called the server, and it waits for a computer to connect to a defined port. The receiving computer is called a client, and it establishes the connection with the server and receives the requested message. TCP is the most commonly used transmission protocol, because it ensures that data is received if sent. This is why TCP/IP is the protocol used for the Internet, especially for browsing the web. When you load a webpage, your computer establishes a TCP/IP connection with the server, and the server sends that webpage directly to your computer, ensuring a safe, complete delivery.

UDP

UDP stands for the User Datagram Protocol, and describes a connection that sends a packet of bytes. UDP sends a predefined amount of data all at once, and it is hopefully received at the other end. The sending computer is called the talker, and it sends the message to a specific IP address and port. The receiving computer is called the listener, and it “listens” to a given port for received messages. UDP does not guarantee delivery of a packet, but generally transmits the data much faster, due to no need to establish a direct connection. This is why UDP is typically used for large amounts of data.

TCP Client & Server

In this section we will go over the C code for the TCP client and server. Please note that this code is meant for sockets on Unix systems, and therefore may not work on Windows without some modifications. For these modifications, see Winsock documentation.

The complete code for the client and server can be found below:

TCP Client & Server

Server

Let's break down the server code. The first block is where we include all the necessary libraries and define essential constants.

#include <stdio.h>      // Standard Input/Output
#include <stdlib.h>     // Standard library
#include <unistd.h>     // Unix API
#include <errno.h>      // Error library
#include <string.h>     // String library
#include <sys/types.h>  // Unix System types
#include <sys/socket.h> // Unix System sockets
#include <netinet/in.h> // Internet library
#include <netdb.h>      // Internet database
#include <arpa/inet.h>  // Internet library
#include <sys/wait.h>   // Wait library
#include <signal.h>     // Exception/error handler
 
#define PORT "4800"     // the port users will be connecting to
 
#define BACKLOG 10      // how many pending connections queue will hold

Next, we will define a function to read the dead processes from previous programs, in order to free up memory. Don't worry too much about the specifics.

// Function to reap all dead processes
void sigchld_handler(int s)
{
    // waitpid() might overwrite errno, so we save and restore it:
    int saved_errno = errno;
 
    while(waitpid(-1, NULL, WNOHANG) > 0);
 
    errno = saved_errno; // Restore errno
}

Next, we will define a function to get the IP address regardless of IP version.

// Function to get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) { // If IPv4
        return &(((struct sockaddr_in*)sa)->sin_addr); //Return IPv4
    }
 
    return &(((struct sockaddr_in6*)sa)->sin6_addr); //Otherwise return IPv6
}

Now, we will get to the main( ). We will start off with declaring all of our objects, and filling our structs with important info.

    int sockfd, new_fd;  // Listen on sockfd, new connection on new_fd
    struct addrinfo hints, *servinfo, *p; //Holds IP address info
    struct sockaddr_storage their_addr; // Connector's address information
    socklen_t sin_size; // Holds socket size
    struct sigaction sa; // Holds data for reaping dead processes
    int yes = 1; // Boolean reference
    char s[INET6_ADDRSTRLEN]; // Holds the socket IP address in string form
    int rv; // Holds error status for getaddrinfo
 
    memset(&hints, 0, sizeof hints); // Fill out empty addrinfo struct
    hints.ai_family = AF_UNSPEC; // Set family to either IPv4 or IPv6, we don't care
    hints.ai_socktype = SOCK_STREAM; // Set socket type to stream (TCP)
    hints.ai_flags = AI_PASSIVE; // Use my IP

Then, we will get the necessary info from our struct using getaddrinfo.

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) { // If error in getaddrinfo
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); // Print error info
        return 1;
    }

Now that our servinfo struct is filled, we will loop through the linked-list of them until we successfully create and bind to a socket.

    for(p = servinfo; p != NULL; p = p->ai_next) { // Loop through all the results and bind to the first we can
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) { // If error during sock creation
            perror("server: socket"); // Give socket error
            continue;
        }
 
        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes,
                sizeof(int)) == -1) { // If error in getting the option
            perror("setsockopt"); // Give option error
            exit(1);
        }
 
        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) { // If error during bind
            close(sockfd); // Close the socket
            perror("server: bind"); // Give bind error
            continue;
        }
 
        break;
    }

Now that our server is bound to a port successfully, we will free the addrinfo struct, and check if we properly bound to the port by validating the port and listening in to the port.

    freeaddrinfo(servinfo); // Free up this structure
 
    if (p == NULL)  { // If no port bound to p
        fprintf(stderr, "server: failed to bind\n"); // Give failure to bind error
        exit(1);
    }
 
    if (listen(sockfd, BACKLOG) == -1) { // If listen fails
        perror("listen"); // Give listen error
        exit(1);
    }

Now that our server has validated the connection, we will reap all dead processes, and if successful, print that we are now awaiting a connection.

    sa.sa_handler = sigchld_handler; // Reap all dead processes
    sigemptyset(&sa.sa_mask); // Clear processes
    sa.sa_flags = SA_RESTART;
    if (sigaction(SIGCHLD, &sa, NULL) == -1) { // If reap error
        perror("sigaction"); // Give sigaction error
        exit(1);
    }
 
    printf("server: waiting for connections...\n"); // Print waiting statement

In order to accept a connection to the server on the defined socket/port, we will put all of the accept/send code within one while loop. This allows the server to be used for multiple different clients. At the beginning of the loop we accept an incoming connection. This accept call waits until a connection has been requested by the client. If the accept succeeds, then we will convert the IP address into a string and print it along with our successful connection message. Finally, we will attempt to send our message, and then close the port.

    while(1) {  // Main accept() loop
        sin_size = sizeof their_addr;
        new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size); // Accept the connection
        if (new_fd == -1) { // If error during accept
            perror("accept"); // Give accept error
            continue;
        }
 
        inet_ntop(their_addr.ss_family,
            get_in_addr((struct sockaddr *)&their_addr),
            s, sizeof s); // Translate IP address into string form
        printf("server: got connection from %s\n", s); // Print client IP address
 
        if (!fork()) { // This is the child process
            close(sockfd); // Child doesn't need the listener
            if (send(new_fd, "Hello, world!", 13, 0) == -1) // If send error (Note: the message being sent can be changed here)
                perror("send"); // Give send error
            close(new_fd); // Close the connection
            exit(0);
        }
        close(new_fd);  // Close the connection
    }

Note, don't forget that all of the code after the first two blocks belongs in main( ), and that we must return 0 at the end of main( ).

Client

Now, let's go over the client. This program is much simpler, as it only need to handle one connection during its run. We start out with the includes and the constant declarations, just as with the server.

#include <stdio.h> // Standard Input/Output library
#include <stdlib.h> // Standard library
#include <unistd.h> // Unix API
#include <errno.h> // Error library
#include <string.h> // String library
#include <netdb.h> // Internet database
#include <sys/types.h> // System types library
#include <netinet/in.h> // Main internet library
#include <sys/socket.h> // Sockets library
 
#include <arpa/inet.h> // The Internet library
 
#define PORT "4800" // The port client will be connecting to
 
#define MAXDATASIZE 100 // Max number of bytes we can get at once

Now that we have taken care of these, we define the same get_in_addr function from the server.

// Get sockaddr, IPv4 or IPv6
void *get_in_addr(struct sockaddr *sa)
{
    if (sa->sa_family == AF_INET) { // If IPv4
        return &(((struct sockaddr_in*)sa)->sin_addr); // Return IPv4
    }
 
    return &(((struct sockaddr_in6*)sa)->sin6_addr); // Otherwise return IPv6
}

Now we will enter the main( ) function, which takes command line arguments for the IP address of the server. At the beginning of main( ), we will declare some important structures, check the command line arguments, and fill the IP address struct with basic info.

    int sockfd, numbytes; // Stores our socket descriptor, and number of bytes read
    char buf[MAXDATASIZE]; // Data buffer for reading the message
    struct addrinfo hints, *servinfo, *p; // Stores our IP address info
    int rv; // Used for error checking
    char s[INET6_ADDRSTRLEN]; // Used to store IP address of server
 
    if (argc != 2) { // Check CL argument count
        fprintf(stderr,"usage: client hostname\n"); // Give usage if wrong
        exit(1);
    }
 
    memset(&hints, 0, sizeof hints); // Fill out addrinfo with empty data
    hints.ai_family = AF_UNSPEC; // Don't care if IPv4 or IPv6
    hints.ai_socktype = SOCK_STREAM; // TCP type

After this, we use the getaddrinfo function just like in the server.

    if ((rv = getaddrinfo(argv[1], PORT, &hints, &servinfo)) != 0) { // If getaddrinfo fails
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv)); // Display error if failed
        return 1;
    }

Now, we do a similar for loop to the server, where we search through the servinfo linked-list until we properly create and connect to the socket.

    // Loop through all the results and connect to the first we can
    for(p = servinfo; p != NULL; p = p->ai_next) {
        if ((sockfd = socket(p->ai_family, p->ai_socktype,
                p->ai_protocol)) == -1) { // If the socket fails to create properly
            perror("client: socket"); // Give socket error
            continue;
        }
 
        if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) { // If fails to connect
            close(sockfd); // Close the socket
            perror("client: connect"); // Give connection error
            continue;
        }
 
        break;
    }

Now, to ensure we are properly connected, we will check to make sure anything actually connected. Then, we will convert the IP address to a string, and then print it along with the connection message.

    if (p == NULL) { // If nothing ever connected
        fprintf(stderr, "client: failed to connect\n"); // Print connection failure
        return 2;
    }
 
    inet_ntop(p->ai_family, get_in_addr((struct sockaddr *)p->ai_addr),
            s, sizeof s); // Convert IP address into a string
    printf("client: connecting to %s\n", s); // Print connection message and IP address
 
    freeaddrinfo(servinfo); // Free the servinfo structure

Now that we have ensured a proper connection, we will receive the message from the server, add a null-terminator to it, and print the message that was received. Finally, we will close the socket.

    if ((numbytes = recv(sockfd, buf, MAXDATASIZE-1, 0)) == -1) { // If receiving the message fails
        perror("recv"); // Give receive error
        exit(1);
    }
 
    buf[numbytes] = '\0'; // Add null termination
 
    printf("client: received '%s'\n",buf); // Print received string
 
    close(sockfd); // Close the socket

Just as with the server, we must ensure that everything after the first two blocks is in the main( ) function, and that we return 0 at the end.

UDP Talker & Listener

Now that we have established the TCP server & client, we can move onto the UDP talker & listener. The code for these is fairly similar, with only some minor changes.

The code for the UDP Talker & Listener can be found below:

UDP Talker & listener

Final Words

This tutorial's objective was to teach the basics of TCP & UDP communications. Complete source code for the 4 different programs was provided. Once the concepts were conveyed the reader could program and understand their own TCP & UDP communications programs.

Speculating future work derived from this tutorial, includes programming the TCP client & server and the UDP talker & listener to handle low bandwidth and high latency communications. In the big picture, the problem of high-latency or low-bandwidth telerobotics can be solved with this tutorial.

For questions, clarifications, etc, Email: wallad3@unlv.nevada.edu

tcp_udp.1493784348.txt.gz · Last modified: 2017/05/02 21:05 by dwallace