Задать вопрос
@Treeesk

Почему recv не работает для меня должным образом?

Я пишу небольшой HTTP-сервер на C++. Я решил сделать так, чтобы клиент не отключался от сокета после одного запроса, а продолжал взаимодействовать с сервером.

#include <iostream>
#include <cstdlib>
#include <fstream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdbool.h>
#include "funcs.h"
#include "path_processing.h"

int main(int argc, char **argv) {
  // Flush after every std::cout / std::cerr
  std::cout << std::unitbuf;
  std::cerr << std::unitbuf;

  // создание сокета (возвращает файловый дескриптор (указатель на сокет)) AF_INET=IPv4, sock_stream = TCP, 0-возможность OS автоматич. определить протокол для TCP
  int server_fd = socket(AF_INET, SOCK_STREAM, 0);
  if (server_fd < 0) {
   std::cerr << "Failed to create server socket\n";
   return 1;
  }
  
  // Since the tester restarts your program quite often, setting SO_REUSEADDR
  // ensures that we don't run into 'Address already in use' errors
  // Настройка параметров сокета, SO_REUSEADDR=параметр, разрешающий повторно использовать адрес(если без этого, после завершения, если сразу запустить будет ошибка)
  // reuse=включить эту опцию
  int reuse = 1;
  if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
    std::cerr << "setsockopt failed\n";
    return 1;
  }
  
  // Тип данных для хранения адреса сокета. htons() - преобразует unsigned int из порядка байтов машины в порядок байтов сети
  // INADDR_ANY: используется, когда мы не хотим привязывать наш сокет к какому-либо конкретному IP-адресу, а вместо этого заставлять его прослушивать все доступные IP-адреса(Обращаться можно по любому доступному IP адресу на машине)
  sockaddr_in server_addr;
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr = INADDR_ANY;
  server_addr.sin_port = htons(4221);
  
  // Соединение созданного дексриптора с адресом сокета(т.е его Ip и порт). Это как телефон и номер, их нужно связать между собой, чтобы звонить. Привязывает сокет к адресу 0.0.0.0:4221
  if (bind(server_fd, (sockaddr *) &server_addr, sizeof(server_addr)) != 0) {
    std::cerr << "Failed to bind to port 4221\n"; 
    return 1;
  }
  
  // Затем указываем слушать сокет server_fd, чтобы он мог принимать входящие подключения через accept. Максимум 25 клиентов на подключение. 
  int connection_backlog = 25;
  if (listen(server_fd, connection_backlog) != 0) {
    std::cerr << "listen failed\n";
    return 1;
  }
  
  // Заполнится системой при подключении.
  sockaddr_in client_addr;
  int client_addr_len = sizeof(client_addr);
  const int buf_size_client = 1024;
  std::cout << "Waiting for a client to connect...\n";
  for (;;){
    // Блокирующая функция, которая ждет клиента. Когда клиент подключается, возвращает новый сокет(файловый дескриптор, представляющий соединение с клиентом).
    int client_socket  = accept(server_fd, (struct sockaddr *) &client_addr, (socklen_t *) &client_addr_len);
    std::cout << "Client connected\n";
    while (true){
      char buffer[buf_size_client] = { 0 }; 
      // Accepting user requests
      int result = recv(client_socket, buffer, sizeof(buffer), 0);
      std::stringstream response;
      std::string str_buf = std::string(buffer);
      if (result == 0) {
        std::cerr << "Connection closed...\n";
      }

      else if (result < 0){
        std::cerr << "recv failed: " << result << "\n";
        close(client_socket);
      }
      else {
        switch (choose_path(str_buf)) {
          // Normal base path
          case paths::base: {
            base_path(response, client_socket);
            break;
          }

          // path localhost:4221/echo/abc
          case paths::echo:{
            echo_path(response, client_socket, str_buf);
            break;
          }

          // path localhost:4221/user-agent
          case paths::agent:{
            agent_path(response, client_socket, str_buf);
            break;
          }

          // path like localhost:4221/files/{path_to_file}
          case paths::file: {
            file_path(response, client_socket, str_buf);
            break;
          }
          // Bad path
          case paths::def: {
            bad_path(response, client_socket);
          }
        }
      }
      if (str_buf.find("Connection: close") != std::string::npos || str_buf.find("Connection:close") != std::string::npos){
        break;
      }
    }
    close(client_socket);
    std::cout << "Connection closed...\n";
  }
  close(server_fd);
  return 0;
}

По такому запросу:

`curl --http1.1 -v localhost:4221/echo/banana --next localhost:4221/user-agent -H "User-Agent: blueberry/apple-blueberry"`

После выполнения первого моя программа останавливается в `recv`, и я не совсем понимаю, что не так.

Если я отправляю запросы с разных терминалов, они выполняются, но между ними есть соединение спам-фразы, которое вылетает, как я понимаю, при проверке `result == 0`. То есть, оказывается, что мой `recv` не ожидает запроса, и цикл продолжается до тех пор, пока `recv` не увидит новый запрос.
  • Вопрос задан
  • 48 просмотров
Подписаться 1 Простой Комментировать
Пригласить эксперта
Ответы на вопрос 1
15432
@15432
Системный программист ^_^
if (result == 0) {
std::cerr << "Connection closed...\n";
}

Вот тут надо break добавить, чтобы программа перешла дальше в accept и так далее.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы