Monday, June 2, 2014

Create your own web server in C/C++



Here I'll outline steps to create basic web server on Linux in C/C++ using sockets.

Let's begin with list of ingredients:

1. We need to open a socket for listening purpose by calling following function:

   int socket(int domain, int type, int protocol);

2.  Next we need to bind the homeless socket just created to some address (port) using call to this function:

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

3. The trap is set. Now wait for the game.

    int listen(int sockfd, int backlog);

4. Accept a connection as it comes and receive the data:

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
   int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

    ssize_t recv(int sockfd, void *buf, size_t len, int flags);

5. Send your response

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);

6. Shutdown the connection and close the client socket

    int shutdown(int sockfd, int how);

    int close(int fd);

Here's the client code:

 /* This page contains the client program. The following one contains the
 * server program. Once the server has been compiled and started, clients
 * anywhere on the Internet can send commands (file names) to the server.
 * The server responds by opening and returning the entire file requested.
 */

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

void fatal(const char *string)
{
  printf("%s\n", string);
  exit(1);
}

#define SERVER_PORT 12345  /* arbitrary, but client and server must agree */
#define BUF_SIZE 4096   /* block transfer size */

int main(int argc, char **argv)
{
  int c, s, bytes;
  char buf[BUF_SIZE];   /* buffer for incoming file */
  struct sockaddr_in channel;  /* holds IP address */

  if (argc != 2) fatal("Usage: client file-name");

  s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (s < 0) fatal("socket");
  memset(&channel, 0, sizeof(channel));
  channel.sin_family= AF_INET;
  
  inet_pton ( AF_INET, "localhost", &channel.sin_addr );
  channel.sin_port= htons(SERVER_PORT);

  c = connect(s, (struct sockaddr *) &channel, sizeof(channel));
  if (c < 0) fatal("connect failed");
  /* Connection is now established. Send file name including 0 byte at end. */
  write(s, argv[1], strlen(argv[1])+1);

  /* Go get the file and write it to standard output. */
  while (1) {
        bytes = read(s, buf, BUF_SIZE); /* read from socket */
        if (bytes <= 0) exit(0); /* check for end of file */
        write(1, buf, bytes);  /* write to standard output */
  }
}
And the server code:

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

#define SERVER_PORT 12345  /* arbitrary, but client and server must agree */
#define BUF_SIZE 4096   /* block transfer size */
#define QUEUE_SIZE 10

void fatal(const char *string)
{
  printf("%s", string);
  exit(1);
}
 
int main(int argc, char *argv[])
{
  int s, b, l, fd, sa, bytes, on = 1;
  char buf[BUF_SIZE];   /* buffer for outgoing file */
  struct sockaddr_in channel;  /* hold's IP address */

  /* Build address structure to bind to socket. */
  memset(&channel, 0, sizeof(channel)); /* zero channel */
  channel.sin_family = AF_INET;
  channel.sin_addr.s_addr = htonl(INADDR_ANY);
  channel.sin_port = htons(SERVER_PORT);

  /* Passive open. Wait for connection. */
  s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); /* create socket */
  if (s < 0) fatal("socket failed");
  setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));

  b = bind(s, (struct sockaddr *) &channel, sizeof(channel));
  if (b < 0) fatal("bind failed");

  l = listen(s, QUEUE_SIZE);  /* specify queue size */
  if (l < 0) fatal("listen failed");

  /* Socket is now set up and bound. Wait for connection and process it. */
  while (1) {
        sa = accept(s, 0, 0);  /* block for connection request */
        if (sa < 0) fatal("accept failed");

        read(sa, buf, BUF_SIZE); /* read file name from socket */
        printf("[%s]\n", buf );
        /* Get and return the file. */
        fd = open(buf, O_RDONLY); /* open the file to be sent back */
        if (fd < 0) fatal("open failed");

        while (1) {
                bytes = read(fd, buf, BUF_SIZE); /* read from file */
                if (bytes <= 0) break;   /* check for end of file */
                write(sa, buf, bytes);   /* write bytes to socket */
        }
        close(fd);     /* close file */
        close(sa);     /* close connection */
  }
}

 
Based on Tanenbaum, "Computer Networks" 

Clang vs GCC

Yes, Clang is definitely more focused and colorful at error reporting than GCC.

It might compile faster than GCC (though I didn't observe that).

Running my own graphics program showed Clang generated executable to be slightly slower than GCC generated executable. Frame-rate was 0.22% less and CPU utilization was 4.37% less for Clang generated executable.

Here's the raw data for those who are interested:

Clang generated executable:
  44.151 FPS
  Consumed 1 second of CPU time every 9.544 seconds

GCC generated executable:
  44.247 FPS
  Consumed 1 second of CPU time every 9.980 seconds