Warum ist Ping so viel schneller als UDP-Sockets?

Warum ist Ping so viel schneller als UDP-Sockets?

Ich versuche, einen Benchmark durchzuführen, wie schnell UDP-Sockets in meinem lokalen Netzwerk sind.

Durch Pingen meiner lokalen IP-Adresse erhalte ich eine Roundtrip-Zeit von ~80 Mikrosekunden.

Ich habe einen minimalen UDP-Server und -Client geschrieben, um die Latenz wie folgt zu testen. Client und Server laufen auf demselben Host und verwenden dieselbe Netzwerkschnittstelle. Der Client sendet über Datagramm-Sockets einen Zeitwert an den Server, und nach dem Empfang berechnet der Server sofort die Differenz zwischen dem aktuellen Zeitwert und dem empfangenen Zeitwert. Auf diese Weise erhalte ich ~110 Mikrosekunden für eine einfache Fahrt, also insgesamt etwa ~220 Mikrosekunden RTT.

Warum ist Ping so viel schneller als mein Programm?

cc udpserver.c -o udpserver
cc udpclient.c -o udpclient
./udpserver 4321
./udpclient 127.0.0.1 4321

udpserver.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

    void error(char *msg)
    {
        perror(msg);
        exit(1);
    }
    int main(int argc, char *argv[])
    {
         int sockfd, newsockfd, portno;
         struct timeval tval_recv, tval_now;
         struct sockaddr_in serv_addr;
         int n;

     /* Check arguments */
     if (argc < 2) {
         fprintf(stderr,"ERROR, no port provided\n");
         exit(1);
     }

     /* Set up socket */
     sockfd = socket(AF_INET, SOCK_DGRAM, 0);
     if (sockfd < 0)
        error("ERROR opening socket");

     /* Setup socket address for server */
     memset((char *) &serv_addr, '\0', sizeof(serv_addr));
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(atoi(argv[1]));

     /* Bind socket to socket address */
     if(bind(sockfd, (struct sockaddr *) &serv_addr,
              sizeof(serv_addr)) < 0)
              error("ERROR on binding");

     while(1) {
       n = recvfrom(sockfd, &tval_recv, sizeof(tval_recv), 0, NULL, NULL);

       if(n < 0)
         error("ERROR in recvfrom");

       gettimeofday(&tval_now, NULL);

       printf("%ld.%06ld\n", tval_now.tv_sec - tval_recv.tv_sec,
           (long int)(tval_now.tv_usec - tval_recv.tv_usec));
     }

     return 0;
}

udpclient.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

void error(char *msg) {
    perror(msg);
    exit(0);
}

int main(int argc, char **argv) {
    in_addr_t s_addr;
    int sockfd, portno, n;
    struct sockaddr_in serveraddr;
    struct timeval tval;
//    struct timespec ts_current;

    /* Check arguments */
    if (argc != 3) {
       fprintf(stderr,"Usage: %s <hostname> <port>\n", argv[0]);
       exit(0);
    }

    /* Create the socket */
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
        error("Error opening socket");

    /* Set the socket address */
    s_addr = inet_addr(argv[1]);
    portno = atoi(argv[2]);
    memset((char *) &serveraddr, '\0', sizeof(serveraddr));
    memcpy(&serveraddr.sin_addr.s_addr, &s_addr, sizeof(s_addr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));

    /* Send packets */
    while(1) {
      gettimeofday(&tval, NULL);
//      clock_gettime(CLOCK_MONOTONIC, &ts_current);
      n = sendto(sockfd, &tval, sizeof(tval), 0, (struct sockaddr *)&serveraddr,
        sizeof(serveraddr));
      if(n < 0)
        error("ERROR in sendto");
      sleep(1);
    }

    return 0;
}

Antwort1

Wilde Vermutung:

pingist ein Tool, das das icmpProtokoll verwendet. icmpBefindet sich auf Schicht 3, der Netzwerkschicht, des OSI-Modells.

udpclient.cist ein Tool, das das udpProtokoll verwendet. udpBefindet sich auf Schicht 4, der Transportschicht des OSI-Modells.

Mit jeder Schicht werden den Rohdaten zusätzliche Daten hinzugefügt. Ein einfaches Beispiel: Ein icmpPaket enthält eine Quell-IP-Adresse und eine Ziel-IP-Adresse. Ein udpDatagramm enthält all das plus z. B. die UDP-Port-Informationen. Diese Port-Informationen müssen von einer höheren Ebene im Netzwerkstapel interpretiert werden.

UDP-Pakete müssen also eine Ebene weiter nach oben im Netzwerkstapel kriechen. Man könnte sich vorstellen, die Treppe zum 4. Stock statt zum 3. Stock zu nehmen. Das dauert länger, allerdings deutlich länger als 30µs.

Ich glaube, diese 30µs bestehen aus

  • Datengröße icmpvs.udp
  • NIC-/Treiber-/CPU-Zeit für die Kapselung und Entkapselung
  • CPU-Zeit, die udpclient.cverwendet

verwandte Informationen