【问题标题】:C++ non blocking socket select send too slow?C ++非阻塞套接字选择发送太慢?
【发布时间】:2012-03-24 17:02:52
【问题描述】:

我有一个维护“流式”套接字列表的程序。这些套接字被配置为非阻塞套接字。

目前,我使用一个列表来存储这些流式套接字。我有一些数据需要发送到所有这些流式套接字,因此我使用迭代器循环遍历这个流式套接字列表并调用下面的 send_TCP_NB 函数:

问题是我自己的程序缓冲区在发送到此 send_TCP_NB 函数之前存储数据的空闲大小缓慢减少,表明发送速度比将数据放入程序缓冲区的速率慢。程序缓冲区的速率约为每秒 1000 个数据。每个数据都很小,大约 100 字节。

因此,我不确定我的 send_TCP_NB 功能是否有效或正确?

int send_TCP_NB(int cs, char data[], int data_length) {

    bool sent = false;
    FD_ZERO(&write_flags);      // initialize the writer socket set
    FD_SET(cs, &write_flags);   // set the write notification for the socket based on the current state of the buffer
    int status;
    int err;

    struct timeval waitd;       // set the time limit for waiting
    waitd.tv_sec = 0;
    waitd.tv_usec = 1000;

    err = select(cs+1, NULL, &write_flags, NULL, &waitd);
    if(err==0)
    {
        // time limit expired
        printf("Time limit expired!\n");
        return 0;   // send failed
    }
    else
    {
        while(!sent)
        {
                if(FD_ISSET(cs, &write_flags))
                {
                     FD_CLR(cs, &write_flags);
                     status = send(cs, data, data_length, 0);
                     sent = true;
                }
         }

         int nError = WSAGetLastError();
         if(nError != WSAEWOULDBLOCK && nError != 0)
         {      
              printf("Error sending non blocking data\n");
              return 0;
         }
         else
         {
              if(nError == WSAEWOULDBLOCK)
              {
                    printf("%d\n", nError);
              }
              return 1;
          }
       }
}

【问题讨论】:

  • 一点无关紧要,如果send的返回是SOCKET_ERROR,你应该只检查WSAGetLastError。此外,send 可能无法一次发送所有数据,因此在循环中调用它(增加数据指针并减小大小)直到发送所有数据。此外,确实不需要 select 调用,但它确实告诉您是否可以发送至少一个字节而不会阻塞。并且不需要围绕发送进行循环,如果套接字在select 之后不在write_flags 中,您将永远循环。
  • @JoachimPileborg 关于发送可能无法一次性发送所有内容这一事实的好点,我错过了。

标签: c++ sockets select send nonblocking


【解决方案1】:

如果你想出准确这个函数应该做什么,这会有所帮助。它实际做的可能不是你想要的,并且有一些不好的功能。

我注意到它的主要功能是:

  1. 修改一些全局状态
  2. 等待(最多 1 毫秒)写入缓冲区有一些空白空间
  3. 如果缓冲区仍满则中止
  4. 在套接字上发送 1 个或更多字节(忽略发送了多少)
  5. 如果有错误(包括发送决定它会被阻止,尽管之前的检查),获取它的值。否则,获取随机误差值
  6. 可能会在屏幕上打印一些内容,具体取决于获得的值
  7. 根据错误值返回 0 或 1。

对这些点的评论:

  1. 为什么write_flags 是全局的?
  2. 真的打算阻止此功能吗?
  3. 这可能没问题
  4. 您确定关心发送了多少数据?
  5. 我在文档中没有看到任何暗示如果send 成功,这将是零

如果你弄清楚这个函数的实际意图是什么,那么确保这个函数真正实现这个意图可能会容易得多。

说的是

我有一些数据需要发送到所有这些流式套接字

究竟你需要什么?

如果您需要在继续之前必须发送数据,那么使用非阻塞写入是不合适的*,因为无论如何您都必须等到可以写入数据。

如果您的需求是数据必须在将来的某个时间发送,那么您的解决方案缺少一个非常关键的部分:您需要为每个套接字创建一个缓冲区来保存需要发送的数据,然后您定期需要调用一个检查套接字的函数来尝试编写它可以写的任何东西。如果你为后一个目的生成一个新线程,这就是select 非常有用的事情,因为你可以让这个新线程阻塞,直到它能够写一些东西。但是,如果您不生成新线程而只是定期从主线程调用一个函数进行检查,那么您就不需要费心了。 (只要写你能写的东西,即使它是零字节)

*:至少,这是一个非常不成熟的优化。在某些边缘情况下,您可以通过智能地使用非阻塞写入来获得更高的性能,但是如果您不了解这些边缘情况是什么以及非阻塞写入将如何提供帮助,那么猜测它不太可能取得好成绩。

编辑:正如另一个答案所暗示的那样,无论如何,这是操作系统擅长的事情。如果您发现套接字缓冲区已满,请不要尝试编写自己的代码来管理此问题,那么请使系统缓冲区更大。如果他们仍然填满,你应该真的认真考虑你的程序无论如何都需要阻塞的想法,以便它停止发送数据的速度比另一个更快end 可以处理它。即对所有数据使用普通阻塞 sends。

【讨论】:

  • +1 原来的代码确实在途中失去了目标。
【解决方案2】:

一些一般性建议:

  • 请记住,您是在乘以数据。因此,如果您获得 1 MB/s 的速度,您会在 N 个客户端上输出 N MB/s。你确定你的网卡能用吗?更小的数据包会变得更糟,你会得到更多的一般开销。您可能需要考虑广播。

  • 您正在使用非阻塞套接字,但您在它们不空闲时阻塞。如果你想不阻塞,最好在套接字没有准备好的情况下立即丢弃数据包。

  • 最好一次“选择”多个套接字。做你正在做的所有事情,但对于所有可用的套接字。您将写入每个“就绪”套接字,然后在有未就绪的套接字时再次重复。这样,您将首先处理可用的套接字,然后有机会,繁忙的套接字将变为可用。

  • while (!sent) 循环没用,而且可能有问题。由于您只检查一个套接字 FD_ISSET 将始终为真。在FD_CLR之后再次检查FD_ISSET是错误的

  • 1234563 /p>
  • 有一些套接字库可能会比你在合理时间内实现的更好(我知道的是boost::asiozmq)。

  • 如果您需要自己实现它(例如,因为 zmq 有自己的数据包格式),请考虑使用线程池库。

编辑:

  • 睡眠 1 毫秒可能是个坏主意。您的线程可能会被取消调度,并且在您再次获得一些 CPU 时间之前需要更多的时间。

【讨论】:

  • 我已经在 127.0.0.1 上尝试过,这样可以减轻网卡的负载吗?关于广播,是否可以使用 TCP,因为我认为这种技术更适用于 UDP。
  • 是的负载。不,使用 TCP 是不可能的,但 UDP 有时是一个不错的选择。特别是如果您在代码中不时丢弃数据包。请注意,有可靠的多播实现(如 PGM 和 EPGM)。它们不像 UDP 那样容易丢失数据包。
【解决方案3】:

这只是一种可怕的做事方式。 select 没有任何用处,只会浪费时间。如果send 是非阻塞的,它可以在部分发送时破坏数据。如果它被阻塞,你仍然会浪费任意多的时间来等待一个接收器。

您需要选择一个明智的 I/O 策略。这是一个:将所有套接字设置为非阻塞。当您需要向套接字发送数据时,只需调用write。如果所有数据都写入,那太好了。如果没有,请保存未发送的数据部分以备后用,并将套接字添加到您的写入集中。当您无事可做时,请致电select。如果您在写入集中的任何套接字上受到命中,请从您保存的内容中写入尽可能多的字节。如果你写了所有这些,从写集中删除那个套接字。

(如果您需要写入已在写入集中的数据,只需将数据添加到要发送的已保存数据中即可。如果缓存的数据过多,您可能需要关闭连接。)

一个更好的主意可能是使用已经完成所有这些事情的库。 Boost::asio 是一个不错的选择。

【讨论】:

    【解决方案4】:

    您在调用send() 之前先调用select()。反过来做。仅当send() 报告WSAEWOULDBLOCK 时才调用select(),例如:

    int send_TCP_NB(int cs, char data[], int data_length)
    { 
        int status; 
        int err; 
        struct timeval waitd;
    
        char *data_ptr = data;
        while (data_length > 0)
        {
            status = send(cs, data_ptr, data_length, 0); 
            if (status > 0)
            {
                data_ptr += status;
                data_length -= status;
                continue;
            }
    
            err = WSAGetLastError();
            if (err != WSAEWOULDBLOCK)
            {
                printf("Error sending non blocking data\n"); 
                return 0;   // send failed 
            }
    
            FD_ZERO(&write_flags);
            FD_SET(cs, &write_flags);   // set the write notification for the socket based on the current state of the buffer 
    
            waitd.tv_sec = 0; 
            waitd.tv_usec = 1000; 
    
            status = select(cs+1, NULL, &write_flags, NULL, &waitd); 
            if (status > 0) 
                continue;
    
            if (status == 0)
                printf("Time limit expired!\n"); 
            else
                printf("Error waiting for time limit!\n"); 
    
            return 0;   // send failed 
        }
    
        return 1; 
    } 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-24
      • 1970-01-01
      • 1970-01-01
      • 2013-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多