如何在 C++ 中正确确定最快的 CDN、镜像、下载服务器

How to correctly determine fastest CDN, mirror, download server in C++

我正在努力解决的问题是如何在 c++ 中确定哪个服务器的客户端连接速度最快 gittarball 克隆或下载.所以基本上我想从 已知 镜像集合中选择一个将用于从中下载内容的镜像。

Following code I wrote demonstrates that what I am trying to achieve more clearly perhaps, but I believe that's not something one should use in production :).

假设我有两个已知的源镜像 git-1.exmple.comgit-2.example.com,我想从客户端连接性最好的源镜像下载 tag-x.tar.gz

CDN.h

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/time.h>
using namespace std;

class CDN {
public:
    long int dl_time;
    string host;
    string proto;
    string path;
    string dl_speed;
    double kbs;
    double mbs;
    double sec;
    long int ms;
    CDN(string, string, string);
    void get_download_speed();
    bool operator < (const CDN&);
};
#endif

CDN.cpp

#include "CND.h"
CDN::CDN(string protocol, string hostname, string downloadpath)
{
    proto = protocol;
    host = hostname;
    path = downloadpath;
    dl_time = ms = sec = mbs = kbs = 0;
    get_download_speed();
}
void CDN::get_download_speed()
{
    struct timeval dl_started;
    gettimeofday(&dl_started, NULL);
    long int download_start = ((unsigned long long) dl_started.tv_sec * 1000000) + dl_started.tv_usec;
    char buffer[256];
    char cmd_output[32];
    sprintf(buffer,"wget -O /dev/null --tries=1 --timeout=2 --no-dns-cache --no-cache %s://%s/%s 2>&1 | grep -o --color=never \"[0-9.]\+ [KM]*B/s\"",proto.c_str(),host.c_str(),path.c_str());
    fflush(stdout);
    FILE *p = popen(buffer,"r");

    fgets(cmd_output, sizeof(buffer), p);
    cmd_output[strcspn(cmd_output, "\n")] = 0;
    pclose(p);

    dl_speed = string(cmd_output);
    struct timeval download_ended;
    gettimeofday(&download_ended, NULL);
    long int download_end = ((unsigned long long)download_ended.tv_sec * 1000000) + download_ended.tv_usec;

    size_t output_type_k = dl_speed.find("KB/s");
    size_t output_type_m = dl_speed.find("MB/s");

    if(output_type_k!=string::npos) {
        string dl_bytes = dl_speed.substr(0,output_type_k-1);
        double dl_mb = atof(dl_bytes.c_str()) / 1000;
        kbs = atof(dl_bytes.c_str());
        mbs = dl_mb;
    } else if(output_type_m!=string::npos) {
        string dl_bytes = dl_speed.substr(0,output_type_m-1);
        double dl_kb = atof(dl_bytes.c_str()) * 1000;
        kbs = dl_kb;
        mbs = atof(dl_bytes.c_str());
    } else {
        cout << "Should catch the errors..." << endl;
    }
    ms = download_end-download_start;
    sec = ((float)ms)/CLOCKS_PER_SEC;
}
bool CDN::operator < (const CDN& other)
{
    if (dl_time < other.dl_time)
       return true;
    else
       return false;
}

main.cpp

#include "CDN.h"
int main() 
{
    cout << "Checking CDN's" << endl;
    char msg[128];
    CDN cdn_1 = CDN("http","git-1.example.com","test.txt");
    CDN cdn_2 = CDN("http","git-2.example.com","test.txt");
    if(cdn_2 > cdn_1)
    {
        sprintf(msg,"Downloading tag-x.tar.gz from %s %s since it's faster than %s %s",
        cdn_1.host.c_str(),cdn_1.dl_speed.c_str(),cdn_2.host.c_str(),cdn_2.dl_speed.c_str());
        cout << msg << endl;

    }
    else
    {
        sprintf(msg,"Downloading tag-x.tar.gz from %s %s since it's faster than %s %s",
        cdn_2.host.c_str(),cdn_2.dl_speed.c_str(),cdn_1.host.c_str(),cdn_1.dl_speed.c_str());
        cout << msg << endl;
    }
    return 0;
}

那么你有什么想法,你会如何处理这个问题。有什么替代方法可以替代此 wget 并在 c++

中实现相同的干净方式

编辑: 正如@molbdnilo 正确指出的

ping measures latency, but you're interested in throughput.

因此我编辑了演示代码以反映这一点,但问题仍然存在

对于初学者来说,试图确定 "fastest CDN mirror" 是一门不精确的科学。 "fastest" 的含义没有普遍接受的定义。在这里,最多可以希望的是为 "fastest" 的含义选择一个合理的启发式,然后在这种情况下尽可能精确地衡量这个启发式。

在此处的代码示例中,所选择的试探法似乎是通过 HTTP 从每个镜像下载示例文件需要多长时间。

实际上,这并不是一个糟糕的选择。您可以合理地论证其他一些启发式方法可能稍微好一些,但我认为从每个候选镜像传输样本文件需要多长时间的基本测试是一种非常合理的启发式方法。

我在这里看到的大问题是这个启发式的实际实现。这种尝试——为示例下载计时——的方式,在这里,似乎不是很可靠,它最终会测量一大堆与网络带宽无关的无关因素。

我在这里至少看到了几个机会,其中与网络吞吐量完全无关的外部因素会破坏测量的时间,并使它们不如应有的可靠。

那么,让我们看一下代码,看看它是如何尝试测量网络延迟的。这是它的主要内容:

sprintf(buffer,"wget -O /dev/null --tries=1 --timeout=2 --no-dns-cache --no-cache %s://%s/%s 2>&1 | grep -o --color=never \"[0-9.]\+ [KM]*B/s\"",proto.c_str(),host.c_str(),path.c_str());
fflush(stdout);
FILE *p = popen(buffer,"r");

fgets(cmd_output, sizeof(buffer), p);
cmd_output[strcspn(cmd_output, "\n")] = 0;
pclose(p);

... gettimeofday() 用于在前后对系统时钟进行采样,以确定这花费了多长时间。太棒了。但这实际上衡量的是什么?

这对这里很有帮助,拿一张白纸,一步一步地写下这里发生的一切作为 popen() 调用的一部分:

1) fork() 编辑了一个新的子进程。操作系统内核创建一个新的子进程。

2)新的子进程exec()s /bin/bash,或者你的默认系统shell,传入一个以"wget"开头的长字符串,后面跟着通过上面看到的一堆其他参数。

3) 操作系统内核加载“/bin/bash”作为新的子进程。内核加载并打开系统 shell 通常需要 运行.

的所有共享库

4) 系统shell进程初始化。它读取 $HOME/.bashrc 文件并执行它,很可能连同您的系统 shell 通常执行的任何标准 shell 初始化文件和脚本一起执行。在新系统 shell 进程实际开始之前,它本身可以创建一堆必须初始化和执行的新进程...

5) ...解析它最初作为参数收到的 "wget" 命令,然后 exec() 执行它。

6) 操作系统内核现在加载 "wget" 作为新的子进程。内核加载并打开 wget 进程需要的所有共享库。查看我的 Linux 框,"wget" 加载不少于 25 个独立的共享库,包括 kerberos 和 ssl 库。这些共享库中的每一个都被初始化。

7) wget 命令在主机上执行 DNS 查找,以获取要连接的 Web 服务器的 IP 地址。如果本地 DNS 服务器没有缓存 CDN 镜像的主机名的 IP 地址,通常需要几秒钟的时间来查找 CDN 镜像的 DNS 区域的权威 DNS 服务器,然后向它们查询 IP 地址,这样跳来跳去,跨越内管。

现在,稍等一下……我好像忘记了我们在这里试图做什么……哦,我记得:哪个 CDN 镜像是 "fastest",通过从每个镜像下载一个示例文件,正确的?是的,一定是它!

现在,上面完成的所有工作与确定哪个内容镜像最快有什么关系???

呃……不多,从我看来。现在,上面的none真该是这么震撼的消息了。毕竟,所有这些都在 popen() 的手册页中进行了描述。如果您阅读 popen 的手册页,它会告诉您那是……它的作用。启动一个新的子进程。然后执行系统shell,以执行请求的命令。等等等等……

现在,我们不是在谈论测量持续数秒或数分钟的时间间隔。如果我们试图测量需要很长时间执行的东西,popen() 方法的相对开销可以忽略不计,不用担心。但下载样本文件的预期时间,目的是弄清楚每个内容镜像有多快——我预计实际下载时间会相对较短。但在我看来,以这种方式执行的开销,分叉一个全新的进程,首先执行系统 ​​shell,然后 wget 命令及其大量的依赖项列表,在统计上将是显着的.

正如我在开头提到的,鉴于这是试图确定模糊不清的让步"fastest mirror" 的 t,这已经是一门不精确的科学——在我看来,你真的想在这里摆脱尽可能多的完全不相关的开销——尽可能多,以便获得尽可能准确的结果。

所以,在我看来,除了您要测量的内容:网络带宽之外,您真的不想在这里测量任何其他内容。而且您当然不想测量任何网络 activity 发生之前发生的任何事情。

我仍然认为尝试为样本下载计时是一个合理的提议。这里不合理的是 popenwget 膨胀。所以,忘掉这一切吧。扔出去window。您想要测量从每个候选镜像下载示例文件需要多长时间?HTTP?好吧,你为什么不 就那样

1) 创建一个新的 socket().

2) 使用getaddrinfo()进行DNS查询,得到候选镜像的IP地址。

3) connect() 到镜像的 HTTP 端口。

4) 格式化合适的HTTP GET request,发送到服务器

到目前为止,上面的内容几乎与 popen/wget 所做的一样。

现在我会通过抓取当前 gettimeofday() 来启动时钟 运行ning,然后等到我从套接字中读取整个示例文件,然后抓取当前 gettimeofday() 再次获取传输结束时间,然后计算从镜像接收文件的实际时间。

只有到那时,我才会有合理的信心,我会实际测量从 CDN 镜像接收样本文件所花费的时间,并完全忽略执行一堆完全不相关的进程所花费的时间;然后通过从多个 CDN 镜像中获取相同的样本,希望尽可能多地使用明智的启发式方法来选择一个。