ping が UDP ソケットよりもはるかに高速なのはなぜですか?

ping が UDP ソケットよりもはるかに高速なのはなぜですか?

ローカル ネットワーク上の UDP ソケットの速度をベンチマークしようとしています。

ローカル IP アドレスに ping を実行すると、往復時間は約 80 マイクロ秒になります。

レイテンシをベンチマークするために、次のように最小限の UDP サーバーとクライアントを作成しました。クライアントとサーバーは同じホストで実行され、同じネットワーク インターフェイスを使用しています。クライアントはデータグラム ソケット経由でサーバーに timeval を送信し、サーバーは受信するとすぐに現在の timeval と受信した timeval の差を計算します。これにより、片道で約 110 マイクロ秒、合計で約 220 マイクロ秒の RTT が得られます。

なぜ ping は私のプログラムよりもはるかに速いのでしょうか?

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;
}

答え1

当てずっぽう:

pingプロトコルを使用するツールですicmp。OSIicmpモデルの第 3 層、ネットワーク層に位置します。

udpclient.cプロトコルを使用するツールですudp。OSIudpモデルの第 4 層、トランスポート層に位置します。

各レイヤーでは、生データに追加データが加えられます。基本的な例として、icmpパケットには送信元 IP アドレスと宛先 IP アドレスが含まれます。udpデータグラムにはこれらすべてに加えて、たとえば UDP ポート情報が含まれます。このポート情報は、ネットワーク スタックの上位レベルで解釈される必要があります。

したがって、UDP パケットはネットワーク スタック内でさらに 1 レベル上まで這い上がる必要があります。3 階ではなく 4 階まで階段を上るようなものです。時間はかかりますが、30µs よりはるかに長くなります。

この30µsは

  • データサイズicmpudp
  • カプセル化とカプセル化解除のための NIC / ドライバー / CPU 時間
  • CPU時間のudpclient.c使用

関連情報