Writing a Webserver in C

Reference implementation — the core poll(2) event loop behind a small C webserver, kept minimal for clarity.

The main receive loop

Writing a webserver in C comes down to this loop: one poll(2) call per iteration, dispatching on which file descriptors are ready. It's deliberately stripped of buffering and parsing detail — those live in the surrounding source, not in this excerpt.

int main(int argc, const char ** argv) {

  // setup the server for listening
  int httpfd = init_serverfd(8080);

  struct pollfd fds[MAX_CLIENTS+1] = {0};

  // add the serverfd to the list of fd's to listen to
  fds[0] = (struct pollfd){.fd = httpfd, .events = POLLIN};
  int nfds = 1;

  while (running) {

    // setup the file descriptor query
    for (int i = 0; i != nfds; i++) {
      fds[i].events = 0;
      if (clients[i].out.len != 0){
        fds[i].events |= POLLOUT;
      }
      fds[i].events |= POLLIN;  
      fds[i].revents = 0;
    }

    // Ask the OS for updates on the fd's
    int n_ready_fds = poll(fds, nfds, -1);

    // Go over the fd's and check if there are received events
    for (int i = 0; i != nfds; i++) {

      // check if there are updates on the serverfd
      // if there are updates these are new clients
      if (fds[i].fd == httpfd) {
        if (fds[i].revents & POLLIN) {

          // accept the new connection
          struct sockaddr_in cli;
          socklen_t len = sizeof(cli);
          int connfd = accept(httpfd, (struct sockaddr *)&cli, &len);

          if (connfd < 0) {
            fprintf(stderr, "FAILURE: received errorcode on accept call");
            fprintf(stderr, "Error message: %s\n", strerror(errno));
          } else {
            // initialize the client and add it to the list of fd's
            client_init(&clients[nfds]);
            fds[nfds++] = fd;
          }
        }
      } else if (fds[i].revents & POLLHUP) {

        // if connection is shut down
        shutdown(fds[i].fd, SHUT_RDWR);
        close(fds[i].fd);

        clients_cleanup(&fds, &clients, i);
        i--;
        nfds--;

      } else if (fds[i].revents & POLLIN ) {
        ClientStatus status = CLOSE;
        status = client_handle_http_receive(fds[i].fd, ht, &clients[i]);

        if (status == CLOSE) {
          shutdown(fds[i].fd, SHUT_RDWR);
          close(fds[i].fd);

          clients_cleanup(&fds, &clients, i);
          i--;
          nfds--;
        }

      } else if (fds[i].revents & POLLOUT) {
        ClientStatus status = client_output_flush(fds[i].fd, &clients[i], clients[i].keepalive);
        if (status == CLOSE) {
          shutdown(fds[i].fd, SHUT_RDWR);
          close(fds[i].fd);

          clients_cleanup(&fds, &clients, i);
          i--;
          nfds--;
        }
      }
    }
  }
  close(httpfd);
  running = false;
}

Created: 2025-01-11 Sat 17:47

Validate