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
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
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;
}
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
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;
}
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
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
}
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;
}
Future Plans
Moving forward, there are several enhancements and features we plan to implement:
- Multi-threading Support: To handle multiple client connections simultaneously, we will introduce threading capabilities to improve the server's efficiency.
- Dynamic Content Serving: Implement functionality to serve dynamic content by integrating with a lightweight templating engine.
- Logging: Add a logging mechanism to track requests, errors, and server performance.
- Security Features: Explore adding HTTPS support and input validation to enhance security.
- 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!