Berkeley Sockets part II
Implementation

Recitation Overview

This recitation will be a walk through of the following code, which implements last time's code skeleton for a simple web server and client. This code will compile and run (I tested it), so feel free to download it and play with it. One important point for compiling: the libraries for socket calls are not in the standard library directories, so you need to pass -lsocket as a parameter to gcc when compiling any code with socket calls in it. Similarly, you must pass -lnsl for gethostbyname.

Client

We'll start with the client, since it's the simpler of the two.

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>

//Port would be 80 for a real web server, but only root can use ports < 1024
#define PORT 31337
#define BUFLENGTH 256

int main(int argc, char *argv[]) {
  int clientsocket;
  struct sockaddr_in serv_adr;
  struct hostent *host;
  char response[BUFLENGTH];                             //create a read/write buffer

  if (argc < 2) {                                       //check that server is specified
    printf("The server must be specified\n");
    printf("Usage: %s <hostname> \n",argv[0]);
    exit(1);
  }
  host=gethostbyname(argv[1]);                          //get host address from name
  if (host == (struct hostent *) NULL) {
    perror("gethostbyname: ");
    exit(0);
  }
  memset(&serv_adr, 0, sizeof(serv_adr));               //init serv_adr memory to 0
  serv_adr.sin_family=AF_INET;                          //set internet family
  memcpy(&serv_adr.sin_addr, host->h_addr, host->h_length); //set host information from above
  serv_adr.sin_port=htons(PORT);                        //set port
  //create socket: internet family, stream based, let computer decide network method
  if ((clientsocket=socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket creation error");
    exit(0);
  }
  
  //connect to host socket: socket to connect, server info from above, address struct size
  if (connect(clientsocket, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) {
    perror("socket connection error");
    close(clientsocket);
    exit(0);
  }
  
  //send HTTP request for index.html: socket to write to, string, max length
  write(clientsocket, "GET /index.html HTTP/1.0", BUFLENGTH);

  //read reply: socket to read from, char array to write to, max length
  if (read(clientsocket, response, BUFLENGTH) == -1) {      //wait for and read response
    perror("socket read error");
    exit(0);
  }
  printf("%s", response);                               //print response

  close(clientsocket);                                  //close socket

  return 0;
}

Server

Now we are ready to tackle the sever. Note that this is not a very useful server, since it always returns 404 instead of finding requested resources, and it only handles GET requests.

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define BUFLENGTH 256
#define PORT 31337

//function to serve requests, passed an open socket descriptor
void handleRequest(int clnt) {
  char request[BUFLENGTH];

  if (read(clnt, request, BUFLENGTH) == -1) {            //read request
    perror("socket read error");
    exit(0);
  }
  printf("Request received: %s\n", request);            //print request
  if (strncmp("GET", request, 3) == 0) {               //check if starts with "GET"
    write(clnt, "HTTP/1.0 404 Not Found\r\n\r\n", BUFLENGTH); //write 404
  }
  close(clnt);                                          //close socket
}

int main() {
  int srvsock, clntsock, adr_length;
  struct sockaddr_in clnt_adr, serv_adr;

  if ((srvsock=socket(AF_INET, SOCK_STREAM, 0)) == -1) {//create socket
    perror("socket creation error");
    exit(0);
  }

  memset(&serv_adr, 0, sizeof(serv_adr));               //set address info
  serv_adr.sin_family=AF_INET;                          //internet family again
  serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);           //don't care about address
  serv_adr.sin_port=htons(PORT);                        //set port
  //bind socket to address and port
  if (bind(srvsock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) {
    perror("socket binding error");
    close(srvsock);
    exit(0);
  }
  //set up a connection queue: socket to listen on, max connections in queue
  if (listen(srvsock, 5) == -1) {
    perror("socket listening error");
    close(srvsock);
    exit(0);
  }
  while (1) {                                           //loop forever as servers often do
    adr_length=sizeof(clnt_adr);
	
    //wait for a connection, then assign the connection to a new socket descriptor
    //note that here clnt_adr is filled in for us by the accept call, and has client info
    if ((clntsock=accept(srvsock, (struct sockaddr *) &clnt_adr, &adr_length)) == -1) {
      perror ("socket accept error");
      close(srvsock);
      exit(0);
    }
    handleRequest(clntsock);                            //handle the request
    //NOTE: in my pseudo-code I had a fork here, but I didn't bother to code it for this
    //example.  In reality, a child process (or even more realistically, a new thread)
    //would handle the request so the server could accept new connections.
  }
  return 0;
}

Stuart Morgan, 2003