【问题标题】:C Linux Bandwidth Throttling of Application应用程序的 C Linux 带宽限制
【发布时间】:2012-11-13 12:24:44
【问题描述】:

有哪些方法可以尝试在循环中限制 send/sendto() 函数。我正在为我的网络创建一个端口扫描仪,我尝试了两种方法,但它们似乎只能在本地工作(当我在我的家用机器上测试它们时它们工作,但是当我尝试在另一台机器上测试它们时它不想创建适当的节流阀)。

方法一

我最初是在解析 /proc/net/dev 并读取“发送的字节数”属性并以此为基础来确定我的睡眠时间。这在本地有效(睡眠延迟正在调整以调整带宽流量),但是当我在另一台服务器上尝试使用/proc/net/dev 时,它似乎并没有正确调整数据。我在本地扫描的机器上运行dstat,它正在快速输出大量数据。

方法2

然后我尝试跟踪我发送的总字节数并将其添加到total_sent 变量中,我的带宽线程将读取该变量并为其计算睡眠计时器。这也适用于我的本地机器,但是当我在服务器上尝试它时,它说每次我的带宽线程检查 total_sent 时它只发送 1-2 个数据包,使我的带宽线程将睡眠减少到 0,但即使是 0 total_sent 变量并没有因为睡眠时间减少而增加,而是保持不变。

总的来说,我想要一种方法来监控 Linux 计算机的带宽并计算我可以在每个 send/sendto() 套接字调用之前或之后传递到 usleep() 以限制带宽的睡眠时间。

编辑:我忘了提到的其他一些事情是我确实有一个 speedtest 函数来计算机器的上传速度,并且我有 2 个线程。 1个线程根据带宽使用情况调整全局睡眠定时器,线程2将数据包发送到远程机器上的端口以测试它们是否打开并对其进行指纹识别(现在我只是使用带有sendto()的udp数据包进行测试这一切)。

如何使用usleep()send/sendto() 调用实施带宽限制。

编辑:这是我的带宽监控线程的代码。不要担心结构的东西,这只是我将数据传递给线程的方式。

void *bandwidthmonitor_cmd(void *param)
{
  int i = 0;
  double prevbytes = 0, elapsedbytes = 0, byteusage = 0, maxthrottle = 0;

  //recreating my param struct i passed to the thread
  command_struct bandwidth = *((command_struct *)param);
  free(param);

  //set SLEEP (global variable) to a base time in case it was edited and not reset
  SLEEP = 5000;

  //find the maximum throttle speed in kb/s (takes the global var UPLOAD_SPEED
  //which is in kb/s and times it by how much bandwidth % you want to use
  //and devides by 100 to find the maximum in kb/s
  //ex: UPLOAD_SPEED = 60, throttle = 90, maxthrottle = 54
  maxthrottle = (UPLOAD_SPEED * bandwidth.throttle) / 100;
  printf("max throttle: %.1f\n", maxthrottle);

  while(1)
  {
      //find out how many bytes elapsed since last polling of the thread
      elapsedbytes = TOTAL_BYTES_SEND - prevbytes;
      printf("elapsedbytes: %.1f\n", elapsedbytes);

      //set prevbytes to our current bytes so we can have results next loop
      prevbytes = TOTAL_BYTES_SEND;

      //convert our bytes to kb/s
      byteusage = 8 * (elapsedbytes / 1024);

      //throttle control to make it adjust sleep 20 times every 30~ 
      //iterations of the loop
      if(i & 0x40)
      {
           //adjust SLEEP by 1.1 gain
           SLEEP += (maxthrottle - byteusage) * -1.1;//;


           if(SLEEP < 0){
               SLEEP = 0;
           }
           printf("sleep:%.1f\n\n", SLEEP);
      }

      //sleep the thread for a short bit then start the process over
      usleep(25000);
      //increment variable i for our iteration throttling
      i++;
  }

}

我的发送线程只是一个简单的sendto() 例程,在while(1) 循环中发送udp 数据包进行测试。 sock 是我的sockfdbuff 是一个用“A”填充的 64 字节字符数组,sinmy sockaddr_in

  while(1)
  {
    TOTAL_BYTES_SEND += 64;

    sendto(sock, buff, strlen(buff), 0, (struct sockaddr *) &sin, sizeof(sin))


    usleep(SLEEP);
  }

我知道我的套接字函数可以工作,因为我可以在本地机器和远程机器上看到 dstat 中的用法。此带宽代码适用于我的本地系统(所有变量都应更改)但在服务器上我尝试对经过的字节进行测试并没有更改(每次线程迭代始终为 64/128)并导致SLEEP 节流到 0 理论上应该使机器更快地发送数据包,但即使 SLEEP 等于 0 elapsedbytes 仍然是 64/128。我还在 if 语句中编码了 sendto() 函数,检查函数是否返回 -1 并通过 printf-ing 错误代码提醒我,但我所做的测试中没有。

【问题讨论】:

  • “然后我尝试跟踪我发送的总字节数,并将其添加到我的带宽线程将读取并计算睡眠计时器的 total_sent 变量中。” — 这听起来对我来说是正确的,请发布您尝试执行此操作的代码。
  • @AlexeyFeldgendler:我提出了一些我的代码。这可能是互斥锁问题吗?我正在从 2 个线程快速读取和写入同一个变量,没有任何锁定。
  • 作为测试,我将 sendto 行更改为 if((TOTAL_BYTES_SEND += sockto(sock,...)) == -1) { printf("error"); } 并且没有错误,我现在得到 sendto() 函数的真实输出,但我拥有的服务器上传速度为 4000kb/s elapsedbytes 仍然非常低,这会导致 SLEEP 变为 0,这不会像在我的服务器上那样增加 elapsedbytes(睡眠会上下调节以维持计算为 maxthrottle 的 kb/s)
  • 您应该将经过的字节数除以自上次测量以来经过的时间。否则,您得到的是发送的一些随机字节数(在未指定的间隔内),这与 maxthrottle (以 kb/s 为单位)无法直接比较。此外,这段代码充满了竞争条件。您应该使用互斥锁来同步对共享变量的访问。

标签: c linux throttling


【解决方案1】:

这似乎可以通过计算发送线程中的节流睡眠时间来最直接地解决。我不确定我是否看到另一个线程来完成这项工作的好处。

这是一种方法:

选择您将在其中测量发送速率的时间窗口。根据您的目标带宽,这将为您提供该时间段的最大字节数。然后,您可以检查是否在每次 sendto() 之后发送了那么多字节。如果您确实超过了字节阈值,则休眠到窗口结束以执行限制。

这里有一些未经测试的代码展示了这个想法。抱歉,clock_gettime 和 struct timespec 增加了一些复杂性。 Google 有一些不错的代码 sn-ps 用于使用 struct timespec 进行更完整的比较、加法和减法。

#define MAX_BYTES_PER_SECOND (128L * 1024L)
#define TIME_WINDOW_MS 50L
#define MAX_BYTES_PER_WINDOW ((MAX_BYTES_PER_SECOND * TIME_WINDOW_MS) / 1000L)

#include <time.h>
#include <stdlib.h>

int foo(void) {
  struct timespec window_start_time;

  size_t bytes_sent_in_window = 0;
  clock_gettime(CLOCK_REALTIME, &window_start_time);

  while (1) {
    size_t bytes_sent = sendto(sock, buff, strlen(buff), 0, (struct sockaddr *) &sin, sizeof(sin));
    if (bytes_sent < 0) {
      // error handling
    } else {
      bytes_sent_in_window += bytes_sent;

      if (bytes_sent_in_window >= MAX_BYTES_PER_WINDOW) {
        struct timespec now;
        struct timespec thresh;

        // Calculate the end of the window
        thresh.tv_sec = window_start_time.tv_sec;
        thresh.tv_nsec = window_start_time.tv_nsec;
        thresh.tv_nsec += TIME_WINDOW_MS * 1000000;
        if (thresh.tv_nsec > 1000000000L) {
          thresh.tv_sec += 1;
          thresh.tv_nsec -= 1000000000L;
        }

        // get the current time
        clock_gettime(CLOCK_REALTIME, &now);

        // if we have not gotten to the end of the window yet
        if (now.tv_sec < thresh.tv_sec ||
            (now.tv_sec == thresh.tv_sec && now.tv_nsec < thresh.tv_nsec)) {

          struct timespec remaining;

          // calculate the time remaining in the window
          //  - See google for more complete timespec subtract algorithm
          remaining.tv_sec = thresh.tv_sec - now.tv_sec;
          if (thresh.tv_nsec >= now.tv_nsec) {
            remaining.tv_nsec = thresh.tv_nsec - now.tv_nsec;
          } else {
            remaining.tv_nsec = 1000000000L + thresh.tv_nsec - now.tv_nsec;
            remaining.tv_sec -= 1;
          }

          // Sleep to end of window
          nanosleep(&remaining, NULL);
        }

        // Reset counters and timestamp for next window
        bytes_sent_in_window = 0;
        clock_gettime(CLOCK_REALTIME, &window_start_time);
      }
    }
  }
}

【讨论】:

  • 代表我提出一个愚蠢的问题,但是我需要根据我想要使用的总带宽的百分比进行更改以使其节流?我假设 TIME_WINDOW_MS 是变量,但我不太明白如何修改它来满足我的需要。 -lrt 链接器库也是大多数 linux 机器的标准吗?如果不是,我可以改用struct timeval 吗?
  • 感谢分配。这种方法实际上是在限制带宽。我只是不知道如何将 TIME_WINDOW_MS 转换为类似于总允许带宽百分比的时间。
  • 我无法编辑我的最后两个 cmets,但我尝试编辑 MAX_BYTES_PER_SECOND 以等同于节流带宽速度mbps = ((60 * 10) / 100) * 1024,这应该等于 6kb/s 或 6144 字节,但它似乎并没有限制连接回来了。似乎限制连接的是修改 TIME_WINDOW_MS 值并将其增加到 1 秒(1000000)。如果 TIME_WINDOW_MS 变量的增益增加它直到发送的字节数等于我估计的节流带宽,或者应该修改每个窗口的最大字节数就足够了,我是否必须创建某种类型?
  • 抱歉延迟回复。 MAX_BYTES_PER_SECOND 是绝对值的目标带宽。在代码中我发布了它的 128KB。要获得可用带宽的百分比,您必须定义最大值。代码无法检测到这一点。然后,您可以取目标与最大值的比率来确定百分比。您当然可以更改变量以设置百分比和最大值,然后计算目标。所以像#define MAX_BYTES_PER_SECOND (TARGET_PERCENT * TOTAL_AVAIL_BYTES_PER_SECOND).
  • 另外,请注意,我计算窗口时间结束的方式存在错误。我乘以 1000 而不是 1000000 以从毫秒转换为纳秒。我不知道你是否发现了这一点,但它会大大偏离算法。希望对您有所帮助。
【解决方案2】:

如果您想在应用程序级别执行此操作,可以使用 trickle 等实用程序来限制或调整应用程序可用的套接字传输速率。

例如,

trickle -s -d 50 -w 100 firefox

将以 50KB/s 的最大下载速率和 100KB 的峰值检测窗口启动 firefox。更改这些值可能会产生适合您的应用程序测试的结果。

【讨论】:

  • 我讨厌必须在每台我想运行我的应用程序的机器上安装涓流,这就是我尝试以编程方式执行此操作的原因。尽管这对于基本测试以查看我的油门是否匹配涓流油门很有用。谢谢你。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
  • 2017-09-22
  • 2010-09-27
  • 1970-01-01
  • 1970-01-01
  • 2017-11-27
相关资源
最近更新 更多