HTTP (Hypertext Transfer Protocol) is the foundation of data communication on the World Wide Web. It is an application layer protocol that allows web clients (such as browsers) and servers to communicate with each other. HTTP is a stateless protocol, meaning each request from a client to the server is independent and contains all the necessary information for the server to understand it.
- Understand the core concepts of HTTP and socket programming.
- Implement an HTTP server in C using sockets.
- Provide clear examples to demonstrate each step of socket programming.
- Enable multithreading to handle concurrent client requests.
- Design the code to be modular and reusable for future scalability.
- Introduction
- Socket Programming Basics
- HTTP Protocol Overview
- Code Implementation
- Handling URL Routes and HTTP Methods
- Multithreading for Concurrent Requests
- Installation Guide
- Contributing to the Project
- Future Improvements
- Conclusion
- Create a socket
- Bind the socket to an address
- Listen on the address
- Block on
accept()until a connection is made - Read on the connected socket
- Figure out how to respond
- Write back on the connected socket
- Close the connection
- Go back to blocking on
accept()
A socket is like a "doorway" for communication between processes or devices. It can be created using the sys/socket.h header file.
To create a socket in C, use the following function:
int sockfd = socket(domain, type, protocol);sockfd: A file descriptor that acts as a label for the socket.domain: Determines the communication domain:AF_LOCAL: Communication between processes on the same computer.AF_INET: Communication using IPv4.AF_INET6: Communication using IPv6.
type: Specifies how data is sent:SOCK_STREAM: Uses TCP, ensuring data arrives in order and without errors.SOCK_DGRAM: Uses UDP, which is faster but doesn’t guarantee reliability.
protocol: Typically set to0, allowing the default protocol for the specified domain and type to be used.
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}- If the
socket()call succeeds,sockfdwill be a positive integer representing the socket’s file descriptor. - If the
socket()call fails, it returns-1. Check the global variableerrnoto diagnose the issue.
Binding a socket associates it with a specific IP address and port so it can send and receive data. This step is crucial for server sockets, as clients need to know where to connect.
The sockaddr_in structure specifies the socket's address properties for IPv4.
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // Clear the structure
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(PORT); // Port number (convert to network byte order)
server_addr.sin_addr.s_addr = INADDR_ANY; // Bind to all available interfacesUse the bind() function to bind the socket to an address:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
perror("Bind failed");
exit(EXIT_FAILURE);
}sockfd: Socket file descriptor.addr: Pointer to astruct sockaddrcontaining the address.addrlen: Size of the address structure.
Prepare the socket to accept incoming connections using the listen() function:
int listen(int sockfd, int backlog);backlog: Maximum number of pending connections that can be queued.
if (listen(sockfd, 5) < 0) {
perror("Listen failed");
exit(EXIT_FAILURE);
}Calling listen() transitions the server socket into passive mode, waiting for connection requests.
When the server calls the accept() function, it enters a waiting state (blocks) until a client connects.
accept()returns a new socket descriptor representing the communication channel with the client.- Use this new socket descriptor for data exchange with the client.
int new_socket = accept(sockfd, (struct sockaddr *)&client_addr, &addrlen);
if (new_socket < 0) {
perror("Accept failed");
exit(EXIT_FAILURE);
}After establishing the connection, use the read() function to read data sent by the client:
valread = read(new_socket, buffer, 1024 - 1);
printf("%s\n", buffer);buffer: Character array to store the incoming data.1024-1: Ensures space for the null terminator (\0).read(): Returns the number of bytes read. A return value of0indicates the client has closed the connection.
char buffer[1024] = {0};
int valread = read(new_socket, buffer, sizeof(buffer));
printf("Client: %s\n", buffer);Decide on the server’s response based on the data received. This step varies depending on the application logic.
If the server receives "Hello":
if (strcmp(buffer, "Hello") == 0) {
send(new_socket, "Hi there!", strlen("Hi there!"), 0);
}Use the send() function to send data back to the client:
ssize_t send(int sockfd, const void *buffer, size_t len, int flags);buffer: Data to send (e.g., a string).len: Length of the data.flags: Set to0for standard behavior.
send(new_socket, "HTTP/1.1 200 OK\r\n\r\nHello, World!", 40, 0);Close the connection using the close() function to release resources:
close(new_socket);- Closing the connection signals the end of communication.
- The server can continue accepting new connections after closing the current one.
After closing the connection, return to accept() to handle new client connections.
HTTP follows a request-response model where the client sends a request, and the server responds. Each HTTP request/response consists of headers, a body, and sometimes status codes.
- The client sends a request for a resource.
- The server processes the request and sends a response.
- HTTP is stateless, meaning each request is independent.
- GET: Retrieve data.
- POST: Send data to create/modify resources.
- PUT: Replace resource data.
- DELETE: Remove resources.
- HEAD: Retrieve headers only.
- OPTIONS: Describe communication options.
- PATCH: Partially modify resources.
- 200 OK: Request was successful.
- 404 Not Found: Requested resource does not exist.
- 500 Internal Server Error: Server encountered an error.
Client sends:
GET /home HTTP/1.1
Host: localhost
Server responds:
HTTP/1.1 200 OK
Content-Type: text/plain
Welcome to the Home Page!
To handle different routes and HTTP methods dynamically:
- Parse the Request: Extract the URL and method from the client request string.
- Route the Request: Compare the extracted route and method against predefined routes.
char *method = strtok(buffer, " ");
char *urlRoute = strtok(NULL, " ");
if (strcmp(method, "GET") == 0 && strcmp(urlRoute, "/home") == 0) {
// Handle GET /home
send(new_socket, "HTTP/1.1 200 OK\r\n\r\nHome Page", 28, 0);
} else {
send(new_socket, "HTTP/1.1 404 Not Found\r\n\r\n", 28, 0);
}- Use
strcmp()to compare the method and URL. - Respond appropriately based on the matched route.
To handle multiple client requests concurrently, implement multithreading using pthread in C.
#include <pthread.h>
void *handle_request(void *socket_desc) {
int new_socket = *(int *)socket_desc;
char buffer[1024] = {0};
read(new_socket, buffer, sizeof(buffer));
printf("Request: %s\n", buffer);
send(new_socket, "HTTP/1.1 200 OK\r\n\r\nHello, World!", 40, 0);
close(new_socket);
free(socket_desc);
return NULL;
}
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);
// Socket setup code here...
while (1) {
new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen);
pthread_t thread_id;
int *socket_desc = malloc(sizeof(int));
*socket_desc = new_socket;
pthread_create(&thread_id, NULL, handle_request, (void *)socket_desc);
pthread_detach(thread_id);
}
return 0;
}- Use
pthread_create()to spawn a new thread for each client. - Use
pthread_detach()to free thread resources upon completion. - Ensure thread safety when accessing shared resources.
To set up and run the HTTP server on your system, follow these steps:
- Operating System: Any Unix-based system (Linux/MacOS) or Windows with a compatible compiler.
- Compiler: GCC or any C compiler installed.
- Libraries: Ensure the
pthreadlibrary is available.
-
Clone the repository:
git clone https://github.com/your-username/HTTP-Server.git cd HTTP-Server -
Compile the server code:
gcc -o http_server server.c -lpthread
-
Run the server:
./http_server
-
Test the server:
curl http://localhost:8080/home
We welcome contributions to improve the HTTP Server! Please follow the guidelines below:
- Fork the repository.
- Clone your fork locally.
- Create a new branch for your changes.
- Make your changes and commit them.
- Push to your fork and create a pull request.
- Follow a consistent coding style.
- Ensure that your code compiles without warnings or errors.
- Test your changes thoroughly before submitting.
-
Docker Integration:
- Create a Dockerfile to containerize the HTTP server application.
- Ensure compatibility with different environments for easy deployment.
-
Code Modularization:
- Refactor the code into reusable functions and modules.
- Create a library for socket-based HTTP server functionalities.
-
Logging and Monitoring:
- Implement logging for requests and errors.
- Add monitoring for server performance and resource usage.
-
HTTPS Support:
- Integrate SSL/TLS for secure communication.
-
Scalability:
- Add load balancing for handling large numbers of concurrent requests.
- Optimize thread pool management.


