JMU
TCP Socket Programming
An Introduction with Examples in C


Prof. David Bernstein
James Madison University

Computer Science Department
bernstdh@jmu.edu


Review
Connection-Oriented Protocols
TCP Programming
Creating a Socket: socket() socket
int socket(int domain, int type, int protocol)
Purpose:
Create a socket
Details:
domain AF_INET or AF_INET6 for IP
type SOCK_STERAM for TCP
protocol 0 for TCP over IP
Return The file descriptor on success; -1 on error
#include <sys/socket.h> sys/socket.h
Binding to an Address and/or Port: bind() bind
int bind(int fd, const struct sockaddr *addr, socklen_t addrlen)
Purpose:
Associate a socket with a particular address and/or port
Details:
fd The file descriptor of a socket
addr The address/port to bind to
addrlen The length of the address
Return 0 on success; -1 on error
#include <sys/socket.h> sys/socket.h
Marking a Socket as Passive: listen() listen
int listen(int fd, int backlog)
Purpose:
Mark a socket as passive/listening
Details:
fd The file descriptor of the socket
backlog The limit on the number of pending connections handled (i.e., connections initiated before the call to accept()
Return 0 on success; -1 on error
#include <sys/socket.h> sys/socket.h
Waiting for a Connection Request: accept() accept
int accept(int fd, struct sockaddr *addr, socklen_t *addrlen)
Purpose:
Accptes an incoming connection on a passive socket
Details:
fd The file descriptor of the passive socket
addr The address of the active socket that initiated the connection (or NULL if not needed)
addrlen The length of addr
Return The file descriptor for the connection on success; -1 on error
#include <sys/socket.h> sys/socket.h

Notes: (1) accept() blocks (unless there are pending connections). (2) The file descriptor that is returned has an associated active socket. In other words, an active socket is created by the passive socket each time accept() returns.

An Example Passive Party (and Server)
unixexamples/tcp/djia_server.c
        #include <fcntl.h>
#include <netdb.h>
#include <stdio.h>  // For sprintf()
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#include "djia_lib.h"

#define PORT 22805



int main(void)
{
  char response[6];
  float djia;
  int connection_fd, listening_fd;
  struct sockaddr_in address;
  
  // Initialize the address of this host
  memset(&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(PORT);
  address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple

  // Create the socket
  listening_fd = socket(AF_INET, SOCK_STREAM, 0);

  // Bind the socket to a specific port
  bind(listening_fd, (struct sockaddr *)&address, sizeof(struct sockaddr));
  
  // Mark the port as passive
  listen(listening_fd, 0); // No backlog

  // Handle requests (one at a time)
  while (1)
    {
      // Accept the connection
      connection_fd = accept(listening_fd, NULL, NULL);

      // Format the response
      djia = get_djia();
      snprintf(response, 6, "%5.0f", djia);
      
      // Write the response
      write(connection_fd, response, 5);

      // Close the connection
      close(connection_fd);
    }

  // Should close the socket (and use a better loop)
  //close(listening_fd);
  
  return 0;
}
        
Making a Connection Request: connect() connect
int connect(int fd, const struct sockaddr *addr, socklen_t addrlen)
Purpose:
Request a connection to a passive socket
Details:
fd The file descriptor of the active socket making the request
addr The address of the passive socket
addrlen The length of the address of the passive socket
Return 0 on success; -1 on error
#include <sys/socket.h> sys/socket.h
An Example Active Party (and Client)
unixexamples/tcp/djia_client.c
        #include <arpa/inet.h>  // For byte order conversions
#include <fcntl.h>
#include <netdb.h>
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#define PORT 22805


int main(void)
{
  char djia[5];
  int fd;
  struct sockaddr_in server;

  // Initialize the address of the server
  memset(&server, 0, sizeof(server));
  server.sin_family = AF_INET;
  inet_pton(AF_INET, "134.126.125.234", &(server.sin_addr));
  server.sin_port = htons(PORT);

  // Create a socket
  fd = socket(AF_INET, SOCK_STREAM, 0);

  // Initiate a connection with the server
  connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr));

  // Read the response
  read(fd, djia, 5);

  // Display the response
  write(STDOUT_FILENO, djia, 5);
  write(STDOUT_FILENO, "\n", 1);

  // Close the connection
  close(fd);
  
  return 0;
}
        
A "Generic" Active Party
Active/Passive and Client/Server
Application Protocols on TCP
An Example of Half-Duplex Application Protocol
An Example Half-Duplex Protocol - The Server
unixexamples/tcp/balance_server.c
        #include <fcntl.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#include "balance_lib.h"

#define PORT 22803


int main(void)
{
  char challenge_string[2], id_string[2], response_string[2];
  int connection_fd, listening_fd;
  int challenge, id, response;
  struct sockaddr_in address;
  
  // Initialize the address of this host
  memset(&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(PORT);
  address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple

  // Create the socket
  listening_fd = socket(AF_INET, SOCK_STREAM, 0);

  // Bind the socket to a specific port
  bind(listening_fd, (struct sockaddr *)&address, sizeof(struct sockaddr));

  // Mark the port as passive
  listen(listening_fd, 0); // No backlog

  // Handle requests (one at a time)
  while (1)
    {
      // Accept the connection
      connection_fd = accept(listening_fd, NULL, NULL);

      // Read the user ID
      read(connection_fd, id_string, 1);
      id_string[1] = '\0';
      id = atoi(id_string);
      
      // Send the challenge
      challenge_string[0] = get_challenge(id);
      challenge_string[1] = '\0';
      challenge = atoi(challenge_string);
      write(connection_fd, challenge_string, 1);
      
      // Read the response
      read(connection_fd, response_string, 1);
      response_string[1] = '\0';
      response = atoi(response_string);
      
      // Verify and send the appropriate response
      if (check_response(id, challenge, response))
        {
          write(connection_fd, get_balance(id), 5);
        }
      else
        {
          write(connection_fd, "XXXXX", 5);
        }

      // Close the connection
      close(connection_fd);
    }

  // Should close the socket (and use a better loop)
  //close(listening_fd);
  
  return 0;
}
        
An Example Half-Duplex Protocol - The Client
unixexamples/tcp/balance_client.c
        #include <arpa/inet.h>  // For byte order conversions
#include <fcntl.h>
#include <netdb.h>
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#define PORT 22803

int main(void)
{
  char challenge_string[2], id_string[2], response_string[2];
  char balance_string[5];
  int fd;
  struct sockaddr_in server;

  // Initialize the address of the server
  memset(&server, 0, sizeof(server));
  server.sin_family = AF_INET;
  inet_pton(AF_INET, "134.126.125.234", &(server.sin_addr));
  server.sin_port = htons(PORT);

  // Create a socket
  fd = socket(AF_INET, SOCK_STREAM, 0);

  // Initiate a connection with the server
  connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr));

  // Prompt the user for her/his ID
  write(STDOUT_FILENO, "ID: ", 4);
  read(STDIN_FILENO, id_string, 2);
  
  // Send the ID
  write(fd, id_string, 1);
  
  // Receive the challenge
  read(fd, challenge_string, 1);

  // Display the challenge string
  write(STDOUT_FILENO, "Challenge: ", 11);
  write(STDOUT_FILENO, challenge_string, 1);
  write(STDOUT_FILENO, "\n: ", 1);

  // Prompt the user for her/his response
  write(STDOUT_FILENO, "Response:  ", 11);
  read(STDIN_FILENO, response_string, 1);

  // Transmit the response
  write(fd, response_string, 1);
  
  // Read the balance
  read(fd, balance_string, 5);

  // Display the balance
  write(STDOUT_FILENO, "Balance: ", 9);  
  write(STDOUT_FILENO, balance_string, 5);
  write(STDOUT_FILENO, "\n", 1);

  // Close the connection
  close(fd);
  
  return 0;
}
        
An Example of a Stream-Oriented Application Protocol
An Example of a Stream-Oriented Application (cont.)
unixexamples/tcp/course_server.c
        #include <fcntl.h>
#include <netdb.h>
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#include "course_lib.h"

#define PORT 22802

int main(void)
{
  char id[4];
  int connection_fd, listening_fd;
  int i, index, n;
  struct sockaddr_in address;
  
  // Initialize the address of this host
  memset(&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(PORT);
  address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple

  // Create the socket
  listening_fd = socket(AF_INET, SOCK_STREAM, 0);

  // Bind the socket to a specific port
  bind(listening_fd, (struct sockaddr *)&address, sizeof(struct sockaddr));

  // Mark the port as passive
  listen(listening_fd, 0); // No backlog

  // Handle requests (one at a time)
  while (1)
    {
      // Accept the connection
      connection_fd = accept(listening_fd, NULL, NULL);

      // Read the course ID
      read(connection_fd, id, 3);
      id[3] = '\0';

      
      // Write the response
      index = get_index(id);
      if (index < 0)
        {
          write(connection_fd, "No Such Course", 15);
        }
      else
        {
          n = lines[index];
          for (i=0; i<n; i++)
            {
              write(connection_fd, description[index][i], 70);
              write(connection_fd, "\n", 1);
            }
        }
      
      // Close the connection
      close(connection_fd);
    }

  // Should close the socket (and use a better loop)
  //close(listening_fd);
  
  return 0;
}
        
An Example Full-Duplex Application Protocol
An Example Full-Duplex Protocol - The Server
unixexamples/tcp/sync_server.c
        #include <fcntl.h>
#include <pthread.h>
#include <netdb.h>
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#include "sync_lib.h"

#define PORT 22806

// The function to be executed in the thread handling outbound traffic
static void
*outbound(void *arg)
{
  int fd, i;

  fd = (int)arg;
  for (i=0; i<FILE_LENGTH; i++)
    {
      write(fd, read_measurement(i), MESSAGE_LENGTH);
    }

  return NULL;
}


// The function to be executed in the thread handling inbound traffic
static void
*inbound(void *arg)
{
  char message[MESSAGE_LENGTH];
  int  fd, i;

  fd = (int)arg;
  
  for (i=0; i<FILE_LENGTH; i++)
    {
      read(fd, message, MESSAGE_LENGTH);

      // Echo the inbound message
      write(STDOUT_FILENO, "From client: ", 13);
      write(STDOUT_FILENO, message, MESSAGE_LENGTH);
      write(STDOUT_FILENO, "\n", 1);
    }

  return NULL;
}




int main(void)
{
  int connection_fd, listening_fd;
  pthread_t sending_thread, receiving_thread;
  socklen_t client_length;
  struct sockaddr_in address;
  struct sockaddr_in client;

  // Initialize the address of this host
  memset(&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(PORT);
  address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple

  // Create a socket
  listening_fd = socket(AF_INET, SOCK_STREAM, 0);

  // Bind the socket to a port
  bind(listening_fd, (struct sockaddr *)&address, sizeof(struct sockaddr));

  // Mark the socket as passive
  listen(listening_fd, 0); // No backlog

  // Accept a (single) connection
  connection_fd = accept(listening_fd, (struct sockaddr *)&client, &client_length);

  // Start the thread that handles outbound traffic
  pthread_create(&sending_thread,   NULL, outbound, (void *)connection_fd);

  // Start the thread that handles inbound traffic
  pthread_create(&receiving_thread, NULL, inbound,  (void *)connection_fd);

  // Wait for both helper threads to finish
  pthread_join(sending_thread,   NULL);
  pthread_join(receiving_thread, NULL);

  // Close the connection
  close(connection_fd);

  // Close the passive socket
  close(listening_fd);
  
  return 0;
}
        
An Example Full-Duplex Protocol - The Client
unixexamples/tcp/sync_client.c
        #include <arpa/inet.h>  // For byte order conversions
#include <fcntl.h>
#include <pthread.h>
#include <netdb.h>
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#include "sync_lib.h"

#define PORT 22806

// The function to be executed in the thread handling outbound traffic
static void
*outbound(void *arg)
{
  int fd, i;

  fd = (int)arg;
  for (i=0; i<FILE_LENGTH; i++)
    {
      write(fd, read_measurement(i), MESSAGE_LENGTH);
    }

  return NULL;
}


// The function to be executed in the thread handling inbound traffic
static void
*inbound(void *arg)
{
  char message[MESSAGE_LENGTH];
  int  fd, i;

  fd = (int)arg;
  
  for (i=0; i<FILE_LENGTH; i++)
    {
      read(fd, message, MESSAGE_LENGTH);

      // Echo the inbound message
      write(STDOUT_FILENO, "From server: ", 13);
      write(STDOUT_FILENO, message, MESSAGE_LENGTH);
      write(STDOUT_FILENO, "\n", 1);
    }

  return NULL;
}





int main(void)
{
  int fd;
  pthread_t sending_thread, receiving_thread;
  struct sockaddr_in server;
  
  // Initialize the address of the server
  memset(&server, 0, sizeof(server));
  server.sin_family = AF_INET;
  inet_pton(AF_INET, "134.126.125.234", &(server.sin_addr));
  server.sin_port = htons(PORT);

  // Create a socket
  fd = socket(AF_INET, SOCK_STREAM, 0);

  // Establish a connection with the server
  connect(fd, (struct sockaddr *)&server, sizeof(struct sockaddr)); // No backlog

  // Create a thread for the outbound traffic
  pthread_create(&sending_thread,   NULL, outbound, (void *)fd);

  // Create a thread for the inbound traffic
  pthread_create(&receiving_thread, NULL, inbound,  (void *)fd);

  // Wait for both helper threads to finish
  pthread_join(sending_thread,   NULL);
  pthread_join(receiving_thread, NULL);

  // Close the connection
  close(fd);
  
  return 0;
}
        
Concurrency
A Server with Multiple Processes
unixexamples/tcp/multiprocessed_course_server.c
        #include <fcntl.h>
#include <netdb.h>
#include <pthread.h>
#include <stdlib.h> // For exit()
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#include "course_lib.h"

#define PORT 22802

static void
handle_connection(int connection_fd)
{
  char id[4];
  int i, index, n;

  // Read the course ID
  read(connection_fd, id, 3);
  id[3] = '\0';
  
  // Write the response
  index = get_index(id);
  if (index < 0)
    {
      write(connection_fd, "No Such Course", 15);
    }
  else
    {
      n = lines[index];
      for (i=0; i<n; i++)
        {
          write(connection_fd, description[index][i], 70);
          write(connection_fd, "\n", 1);
        }
    }
      
  // Close the connection
  close(connection_fd);
}



int main(void)
{
  int connection_fd, listening_fd;
  int pid;
  struct sockaddr_in address;
  
  // Initialize the address of this host
  memset(&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(PORT);
  address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple

  // Create the socket
  listening_fd = socket(AF_INET, SOCK_STREAM, 0);

  // Bind the socket to a specific port
  bind(listening_fd, (struct sockaddr *)&address, sizeof(struct sockaddr));

  // Mark the port as passive
  listen(listening_fd, 0); // No backlog

  // Handle requests (concurrently, in different processes)
  while (1)
    {
      // Accept the connection
      connection_fd = accept(listening_fd, NULL, NULL);

      // Create a process to handle the connection
      // (Remember: The file descriptors are copied/inherited)
      pid = fork();

      switch(pid)
        {
        case -1:
          close(connection_fd);
          break;
        case 0:  // Child
          close(listening_fd);              // Close the child's copy
          handle_connection(connection_fd); // Handle the connection
          exit(0);                          // Terminate the child
        default: // Parent
          close(connection_fd); // Close the parent's copy
          break;                
        }
    }

  // Should close the socket (and use a better loop)
  //close(listening_fd);
  
  return 0;
}
        
A Server with Multiple Threads
unixexamples/tcp/threaded_course_server.c
        #include <fcntl.h>
#include <netdb.h>
#include <pthread.h>
#include <string.h> // For memset()
#include <sys/socket.h>
#include <unistd.h>

#include "course_lib.h"

#define PORT 22802

static void
*handle_connection(void *args)
{
  char id[4];
  int i, index, n;
  int connection_fd;

  connection_fd = (int)args;

  // Read the course ID
  read(connection_fd, id, 3);
  id[3] = '\0';
  
  // Write the response
  index = get_index(id);
  if (index < 0)
    {
      write(connection_fd, "No Such Course", 15);
    }
  else
    {
      n = lines[index];
      for (i=0; i<n; i++)
        {
          write(connection_fd, description[index][i], 70);
          write(connection_fd, "\n", 1);
        }
    }
      
  // Close the connection
  close(connection_fd);
}



int main(void)
{
  int connection_fd, listening_fd;
  pthread_t  connection_thread;
  struct sockaddr_in address;
  
  // Initialize the address of this host
  memset(&address, 0, sizeof(address));
  address.sin_family = AF_INET;
  address.sin_port = htons(PORT);
  address.sin_addr.s_addr = htonl(INADDR_ANY); // In case the host has multiple

  // Create the socket
  listening_fd = socket(AF_INET, SOCK_STREAM, 0);

  // Bind the socket to a specific port
  bind(listening_fd, (struct sockaddr *)&address, sizeof(struct sockaddr));

  // Mark the port as passive
  listen(listening_fd, 0); // No backlog

  // Handle requests (concurrently, in different threads)
  while (1)
    {
      // Accept the connection
      connection_fd = accept(listening_fd, NULL, NULL);

      // Create a thread to handle the response
      pthread_create(&connection_thread, NULL, handle_connection, (void *)connection_fd);

      // Let the thread execute and terminate independently
      pthread_detach(connection_thread);
 
    }

  // Should close the socket (and use a better loop)
  //close(listening_fd);
  
  return 0;
}
        
Improving Concurrent Servers
Partial Reads/Writes
Partial Reads/Writes (cont.)
unixexamples/tcp/tcpio.c
        #include <errno.h>
#include <unistd.h>
#include "tcpio.h"

ssize_t
readn(int fd, char *buffer, size_t n)
{
  char   *position;
  size_t  received;
  ssize_t remaining;
  
  position  = buffer;
  remaining = n;
  while (remaining > 0)
    {
      received = read(fd, position, remaining);
      if (received  < 0)
        {
          if (errno == EINTR) received = 0; // Interrupted by a signal
          else                return -1;
        }
      else if (received == 0) return n-remaining;

      remaining -= received;
      position  += received;
    }

  return n-remaining;
}



ssize_t
writen(int fd, const char *buffer, size_t n)
{
  const char *position;
  size_t  sent;
  ssize_t remaining;
  
  position  = buffer;
  remaining = n;
  while (remaining > 0)
    {
      sent = write(fd, position, remaining);
      if (sent < 0)
        {
          if (errno == EINTR) sent = 0; // Interrupted by a signal
          else                return -1;
        }
      else if (sent == 0) return -1;

      remaining -= sent;
      position  += sent;
    }

  return n;
}