DEV Community

Trish
Trish

Posted on

Building a Simple TCP Server in C

In this blog post, we’ll explore how to create a simple TCP server in C that can serve HTML files. We will break down the code, explain how it works, and discuss future plans for enhancing this project. This is an excellent example of how you can "just do things" in C without overcomplicating the process!

Project Overview

The goal of this project is to implement a basic TCP server that listens for client connections and serves HTML files upon request. The server will handle client requests, read the specified HTML file, and send the contents back to the client as an HTTP response.

🔗 Keep the conversation going on Twitter(X): @trish_07

🔗 GitHub Repository: Explore the TCP Server Project Repository

Project Structure

To organize our code, we’ll structure the project as follows:

tcp_server_c/
├── CMakeLists.txt             # Build configuration
├── include/
│   ├── server.h               # Main server header file
│   ├── html_serve.h           # Header for serve_html function
│   ├── request_handler.h      # Header for handle_client function
│   └── socket_utils.h         # Header for socket utility functions
├── src/
│   ├── server.c               # Main server program
│   ├── html_serve.c           # serve_html function
│   ├── request_handler.c      # handle_client function
│   └── socket_utils.c         # Utility functions for socket operations
└── README.md                  # Project documentation
Enter fullscreen mode Exit fullscreen mode

Code Breakdown

1. Socket Utilities

First, let's create a utility file to handle socket initialization. This will ensure that our main server code remains clean and focused.

include/socket_utils.h

#ifndef SOCKET_UTILS_H
#define SOCKET_UTILS_H

#include <arpa/inet.h>

int initialize_server(struct sockaddr_in* address);

#endif
Enter fullscreen mode Exit fullscreen mode

src/socket_utils.c

#include "socket_utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define PORT 8080

int initialize_server(struct sockaddr_in* address) {
    int server_fd;
    int opt = 1;

    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket failed!");
        return -1;
    }

    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)) != 0) {
        perror("setsockopt failed");
        close(server_fd);
        return -1;
    }

    address->sin_family = AF_INET;
    address->sin_addr.s_addr = INADDR_ANY;
    address->sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr*)address, sizeof(*address)) < 0) {
        perror("Bind failed!");
        close(server_fd);
        return -1;
    }

    if (listen(server_fd, 3) < 0) {
        perror("Listen failed!");
        close(server_fd);
        return -1;
    }

    return server_fd;
}
Enter fullscreen mode Exit fullscreen mode

2. HTML Serving Functionality

Next, we will create a function to serve HTML files. This function will read the contents of an HTML file and return them to the caller.

include/html_server.h

#ifndef HTML_SERVER_H
#define HTML_SERVER_H

char* serve_html(const char* filename);

#endif
Enter fullscreen mode Exit fullscreen mode

src/html_server.c

#include "html_server.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* serve_html(const char* filename) {
    FILE* file = fopen(filename, "r");
    if (!file) {
        perror("Error opening file");
        return NULL;
    }

    fseek(file, 0, SEEK_END);
    long length = ftell(file);
    fseek(file, 0, SEEK_SET);

    char* buffer = malloc(length + 1);
    if (!buffer) {
        perror("Error allocating memory");
        fclose(file);
        return NULL;
    }

    fread(buffer, 1, length, file);
    buffer[length] = '\0'; // Null-terminate the buffer

    fclose(file);
    return buffer;
}
Enter fullscreen mode Exit fullscreen mode

3. Handling Client Requests

Now, let’s implement the logic to handle incoming client requests.

include/request_handler.h

#ifndef REQUEST_HANDLER_H
#define REQUEST_HANDLER_H

#include <sys/socket.h>

void handle_client(int new_socket);

#endif
Enter fullscreen mode Exit fullscreen mode

src/request_handler.c

#include "request_handler.h"
#include "html_server.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define BUFFER_SIZE 1024

void handle_client(int new_socket) {
    char buffer[BUFFER_SIZE] = { 0 };
    read(new_socket, buffer, BUFFER_SIZE);

    // Serve the HTML file
    char* html_content = serve_html("../html/index.html");
    if (html_content) {
        write(new_socket, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n", 48);
        write(new_socket, html_content, strlen(html_content));
    } else {
        const char* not_found_response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n<h1>404 Not Found</h1>";
        write(new_socket, not_found_response, strlen(not_found_response));
    }

    free(html_content);
    close(new_socket); // Close the connection with the current client
}
Enter fullscreen mode Exit fullscreen mode

4. Main Server Logic

Finally, let’s put everything together in the main file.

src/main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "socket_utils.h"
#include "request_handler.h"

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);

    server_fd = initialize_server(&address);
    if (server_fd == -1) {
        return EXIT_FAILURE;
    }

    printf("Server listening on port: 8080\n");

    while (1) {
        if ((new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) {
            perror("Connection not accepted!");
            continue;
        }
        handle_client(new_socket); // Handle the client request
    }

    close(server_fd);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

Future Plans

Moving forward, there are several enhancements and features we plan to implement:

  1. Multi-threading Support: To handle multiple client connections simultaneously, we will introduce threading capabilities to improve the server's efficiency.
  2. Dynamic Content Serving: Implement functionality to serve dynamic content by integrating with a lightweight templating engine.
  3. Logging: Add a logging mechanism to track requests, errors, and server performance.
  4. Security Features: Explore adding HTTPS support and input validation to enhance security.
  5. Improved Error Handling: Implement better error handling for various scenarios, such as file not found, server overload, etc.

Conclusion

This simple TCP server project serves as a foundational example of how to create a web server in C, demonstrating the language's power and simplicity. By building on this foundation, we can develop more sophisticated features and improve performance, making it a robust solution for serving web content.

You can find the complete source code and contribute to the project on GitHub: GitHub Repository Link.

Feel free to provide feedback, ask questions, or contribute your ideas for future enhancements!


Top comments (0)