Making a simple HTTP server with sockets

-

In this blog post, we’ll set up a very simple server in C. The server will be single-threaded, and it will just write the phrase "Hello world" to the client. The full code is available on GitHub. For context, I’m writing this code on a computer running macOS.

To do so, we’ll open a socket for the server, bind that socket to an address, and listen for client requests. When a client makes a request, we’ll handle it, then close the socket for that client.

Part 1: Setup

First, we need to add a few imports:

#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <netinet/in.h>

We also need one global variable:

int server_fd;

Before we open our server socket and start listening for incoming requests, let’s set up the signal handler to make sure that our server always shuts down gracefully. Right now, since there’s no server socket open, this won’t do anything, but it will be helpful as we proceed. We’ll pass the pointer to our server file descriptor to a function that we’ll write soon, which we’ll call start_server.

void shut_down_server_handler(int signal) {
    close(server_fd);
    exit(0);
}

int main(int argc, char **argv) {
    // Make sure we shut down the server gracefully.
    signal(SIGINT, shut_down_server_handler);

    start_server(&server_fd);
}

Part 2: Writing the start_server function

Here’s the code that we’ll use to start a loop to handle incoming connections:

void start_server(int *server_socket) {
    // Specify the port.
    int port = 8000;

    // Set up the socket for the server.
    *server_socket = socket(PF_INET, SOCK_STREAM, 0);

    // Allow reuse of local addresses.
    int socket_option = 1;
    setsockopt(*server_socket, SOL_SOCKET, SO_REUSEADDR, &socket_option, 
        sizeof(socket_option));

    // Set up the server address struct.
    struct sockaddr_in server_address;
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_addr.s_addr = INADDR_ANY;
    server_address.sin_port = htons(port);

    // Assign the address to the socket.
    bind(*server_socket, (struct sockaddr *) &server_address, 
        sizeof(server_address));

    // Start listening.
    listen(*server_socket, 1024);

    // Initialize the relevant variables to handle client sockets.
    struct sockaddr_in client_address;
    size_t client_address_length = sizeof(client_address);
    int client_socket;

    // Start the server loop.
    while (1) {
        // Accept the client socket.
        client_socket = accept(*server_socket,
            (struct sockaddr *) &client_address,
            (socklen_t *) &client_address_length);

        handle_client(client_socket);

        close(client_socket);
    }

    // Shut down the server.
    shutdown(*server_socket, SHUT_RDWR);
    close(*server_socket);
}

We initially set up the server socket. Then, we initialize a struct of the type sockaddr_in, since we need to pass it to the bind function. Then, we start listening, establishing a maximum length of backlogged connections at 1024.

In the server loop, rather than creating a socket for clients, we accept connections. We pass the file descriptor to the handle_client function, which we’ll shortly implement.

Part 3: Writing the HTTP response

Finally, we just need to actually write the HTTP response.

void handle_client(int fd) {
    // Get our response ready.
    char *data = "Hello world.\n";
    int size = strlen(data);

    // Start the response.
    dprintf(fd, "HTTP/1.0 200 OK\r\n");

    // Set headers.
    // Double line break to indicate the end of headers.
    dprintf(fd, "Content-Type: text/html\r\n");
    dprintf(fd, "Content-Length: %d\r\n\r\n", size);

    // Loop until we're finished writing.
    ssize_t bytes_sent;
    while (size > 0) {
        bytes_sent = write(fd, data, size);
        if (bytes_sent < 0)
            return;
        size -= bytes_sent;
        data += bytes_sent;
    }
}

We start the response by sending a 200 status code, meaning "OK." Then, we declare two mandatory headers, "Content-Type" and "Content-Length." Finally, we write to the client until there’s no more data to write, at which point we return. The server loop closes the client socket.

Conclusion

Now you can compile the code with gcc server.c -o server. Then you can run the server with ./server, and you can direct your browser to http://localhost:8000. You should see "Hello world." printed to your screen.

That’s all there is to it. If you want to expand on this, you might want to handle each client connection on its own thread, so the server doesn’t block. You might also want to incorporate some error handling. There are plenty of improvements that can be made, but this code should get you started pretty quickly.

Tags: simple sockets server http c

See also: Explaining to my parents what I do

Back to all posts

Neel Somani

About the Author

I'm the founder of Eclipse. You can follow me on Twitter.