2 votes

Les sockets HTTP en C se ferment prématurément et semblent générer des erreurs de lecture.

td;lr: essaie d'écho "Hello World" à un client HTTP mais rencontre des problèmes avec la fermeture trop précoce du socket et des erreurs de lecture mystérieuses de l'outil de benchmark wrk.

J'essaie de créer un serveur HTTP simple "Hello World" avec la bibliothèque de boucle d'événements picoev mais la connexion client/peer se ferme trop rapidement et l'outil de benchmark wrk renvoie des erreurs de lecture pour une raison quelconque dont je ne suis pas conscient. Voici le code que j'utilise :

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include "picoev.h"

#define HOST 0 /* 0x7f000001 pour localhost */
#define PORT 8080
#define MAX_FDS 1024 * 128
#define TIMEOUT_SECS 10

char buf[1024];
ssize_t response;
int listen_sock;

static void close_conn(picoev_loop* loop, int fd)
{
  picoev_del(loop, fd);
  close(fd);
}

static void write_callback(picoev_loop* loop, int fd, int events, void* cb_arg)
{
  // vérifie si ni les événements ni les délais ne sont présents
  if ((events & PICOEV_TIMEOUT) != 0) {
    /* délai */
    close_conn(loop, fd);

  } else if ((events & PICOEV_READ) != 0) {
    /* mise à jour du délai, et lecture */
    picoev_set_timeout(loop, fd, TIMEOUT_SECS);
    ret = read(fd, buf, sizeof(buf));

    if (ret == 0 | ret == -1) {
      close_conn(loop, fd);
    }

    else {
      write(fd, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\nConnection: close\r\n\r\nHello, world!", ret);
      close_conn(loop, fd);
    }

  }
}

static void accept_callback(picoev_loop* loop, int fd, int events, void* cb_arg)
{
  int newfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK | SOCK_CLOEXEC);
  if (newfd != -1) {
    picoev_add(loop, newfd, PICOEV_READ, TIMEOUT_SECS, write_callback, NULL);
  }
}

int main(void)
{
  picoev_loop* loop;

  /* écouter sur le port */
  listen_sock = socket(AF_INET, SOCK_STREAM, 0);
  setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, 1, sizeof(1));
  struct sockaddr_in listen_addr;
  listen_addr.sin_family = AF_INET;
  listen_addr.sin_port = htons(PORT);
  listen_addr.sin_addr.s_addr = htonl(HOST);
  bind(listen_sock, (struct sockaddr*)&listen_addr, sizeof(listen_addr));
  listen(listen_sock, 1000000);

  /* initialiser picoev */
  picoev_init(MAX_FDS);
  /* créer une boucle */
  loop = picoev_create_loop(60);
  /* ajouter le socket d'écoute */
  picoev_add(loop, listen_sock, PICOEV_READ, 1, accept_callback, NULL);
  /* boucle */
  while (1) {
    // Appel asynchrone de Picoev pour écrire etc...
    picoev_loop_once(loop, 10);
  }
  /* nettoyer */
  picoev_destroy_loop(loop);
  picoev_deinit();

  return 0;
}

Le lancement avec curl http://0.0.0.0:8080/ -v renvoie :

*   Essai de 0.0.0.0...
* TCP_NODELAY activé
* Connecté à 0.0.0.0 (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: 0.0.0.0:8080
> User-Agent: curl/7.52.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: text/html
< Content-Length: 13
* transfert fermé avec 13 octets restants à lire
* Curl_http_done: appel prématuré == 1
* arrêt du flux de mise en pause!
* Fermeture de la connexion 0
curl: (18) transfert fermé avec 13 octets restants à lire

ou le suivant après avoir essayé de benchmarker des milliers de connexions concurrentes quelques fois après l'autre :

*   Essai de 0.0.0.0...
* TCP_NODELAY activé
* échec de la connexion au port 8080 de 0.0.0.0 : Connection refused
* Échec de la connexion au port 8080 de 0.0.0.0 : Connection refused
* Fermeture de la connexion 0
curl: (7) Échec de la connexion au port 8080 de 0.0.0.0 : Connection refused

et wrk -t1 -c400 http://0.0.0.0:8080/ renvoie toutes les erreurs en lecture:

Exécution d'un test de 10s @ http://0.0.0.0:8080/
  1 thread et 400 connexions
  Statistiques du thread   Moyenne     Ecart-type     Max   +/- Ecart-type
    Latence     0,00us    0,00us   0,00us    -nan%
    Req/Sec     0,00      0,00     0,00      -nan%
  0 requêtes en 10,08s, 9,05MB lues
  Erreurs de socket : connect 0, lecture 249652, écriture 0, expiration 0
Requêtes/sec:      0,00
Transfert/sec:      0,90MB

Je ne comprends pas si le problème vient de la fermeture trop précoce du socket, de la réponse (ret) incorrecte, des fd zombie non tués ou d'une combinaison de ceux-ci. En essayant de faire des traces du programme, je n'obtiens aucune information précieuse sur l'emplacement du problème, juste beaucoup de epoll_wait. J'ai déjà essayé de nombreuses variations de réponses HTTP en vain et comme vous pouvez le voir, j'essaie de tuer tout fd zombie ou en erreur dès que nécessaire mais soit je le fais mal, soit le problème réside ailleurs. Est-ce que quelqu'un pourrait m'aider à localiser le problème là où il se trouve ?

2voto

jxh Points 32720

Dans cette ligne de code :

write(fd, "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 13\r\nConnection: close\r\n\r\nHello, world!", ret);

Vous utilisez ret pour le troisième paramètre de votre appel à write(). Ce paramètre est utilisé pour indiquer à write() combien d'octets doivent être écrits.

Cependant, ret a été utilisé pour stocker le résultat d'un appel à read(). Ainsi, il n'y a pas de relation entre la valeur passée à write() et la taille du message que vous voulez envoyer.

Corrigez cela en initialisant ret avec la longueur du message que vous souhaitez envoyer.

const char *msg = "HTTP/1.1 ...";
ret = strlen(msg);
write(fd, msg, ret);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X