Сервер будет использовать необработанные TCP-сокеты для установления соединений, получения запросов и отправки ответов. Мы сосредоточимся на реализации основной функциональности HTTP-сервера, включая разбор запросов, обработку заголовков и отправку соответствующих ответов.
Код: Выделить всё
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fstream>
#define PORT 8080
#define BUFFER_SIZE 4096
int main() {
// Create a socket
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
std::cerr << "Failed to create socket" << std::endl;
return 1;
}
// Prepare the server address
sockaddr_in server_address;
std::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);
// Bind the socket to the address
if (bind(server_socket, (sockaddr*)&server_address, sizeof(server_address)) == -1) {
std::cerr << "Failed to bind socket" << std::endl;
return 1;
}
// Listen for incoming connections
if (listen(server_socket, 5) == -1) {
std::cerr << "Failed to listen on socket" << std::endl;
return 1;
}
std::cout << "Server is listening on port " << PORT << std::endl;
while (true) {
// Accept an incoming connection
sockaddr_in client_address;
socklen_t client_address_len = sizeof(client_address);
int client_socket = accept(server_socket, (sockaddr*)&client_address, &client_address_len);
if (client_socket == -1) {
std::cerr << "Failed to accept connection" << std::endl;
continue;
}
std::cout << "Accepted connection from " << inet_ntoa(client_address.sin_addr) << ":" << ntohs(client_address.sin_port) << std::endl;
// Receive the HTTP request
char buffer[BUFFER_SIZE];
std::memset(buffer, 0, BUFFER_SIZE);
ssize_t bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received == -1) {
std::cerr << "Failed to receive request" << std::endl;
close(client_socket);
continue;
}
// Parse the request
std::string request(buffer, bytes_received);
std::size_t first_space = request.find(' ');
std::string method = request.substr(0, first_space);
std::size_t second_space = request.find(' ', first_space + 1);
std::string path = request.substr(first_space + 1, second_space - first_space - 1);
std::cout << "Received " << method << " request for " << path << std::endl;
// Handle the request
if (method == "GET") {
// Serve the requested file
std::string file_path = "." + path;
std::ifstream file(file_path, std::ios::binary);
if (file) {
std::string response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n";
response += std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
send(client_socket, response.c_str(), response.length(), 0);
} else {
std::string response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\n\r\n<html><body><h1>404 Not Found</h1></body></html>";
send(client_socket, response.c_str(), response.length(), 0);
}
} else {
std::string response = "HTTP/1.1 501 Not Implemented\r\nContent-Type: text/html\r\n\r\n<html><body><h1>501 Not Implemented</h1></body></html>";
send(client_socket, response.c_str(), response.length(), 0);
}
close(client_socket);
}
close(server_socket);
return 0;
}
1. Мы начинаем с включения заголовков, необходимых для работы в сети, файловых операций и работы со строками.
2. Мы определяем номер порта (`PORT`) и размер буфера (`BUFFER_SIZE`) для приема запросов.
3. В функции `main` мы создаем TCP-сокет с помощью системного вызова `Socket`.
4. Мы подготавливаем адрес сервера, устанавливая `sin_family` в `AF_INET` (IPv4), `sin_addr.s_addr` в `INADDR_ANY` (слушать на всех доступных сетевых интерфейсах), а `sin_port` в нужный номер порта (`PORT`).
5. Мы привязываем сокет к адресу сервера с помощью системного вызова `bind`.
6. Начинаем прослушивать входящие соединения на привязанном сокете с помощью системного вызова `listen`.
7. Мы вводим цикл для непрерывного приема входящих соединений с помощью системного вызова `accept`.
8. Для каждого принятого соединения мы получаем HTTP-запрос с помощью системного вызова `recv` и сохраняем его в буфере.
9. Мы разбираем полученный запрос, чтобы извлечь из него HTTP-метод (например, GET, POST) и запрашиваемый путь.
10. В зависимости от метода мы обрабатываем запрос соответствующим образом. В этом примере мы обрабатываем только GET-запросы и обслуживаем статические HTML-файлы из текущей директории.
11. Если запрашиваемый файл существует, мы считываем его содержимое и отправляем HTTP-ответ со статусом 200 OK и содержимым файла в качестве тела.
12. Если запрашиваемый файл не существует, мы отправляем ответ 404 Not Found.
13. Для любых других методов (например, POST, PUT, DELETE) мы отправляем ответ 501 Not Implemented.
14. После отправки ответа мы закрываем клиентский сокет.