C ++非阻塞套接字选择发送太慢了?

时间:2012-03-07 06:40:05

标签: c++ sockets select send nonblocking

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

目前,我使用了一个列表来存储这些流套接字。我有一些数据需要发送到所有这些流套接字,因此我使用迭代器遍历此流套接字列表并调用下面的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;
          }
       }
}

4 个答案:

答案 0 :(得分:3)

如果您认为完全这个函数应该做什么,那么有一件事会有所帮助。它实际上做的可能不是你想要的,并且有一些不好的功能。

我注意到它的主要特征是:

  1. 修改某些全局状态
  2. 等待(最多1毫秒)写缓冲区有一些空白空间
  3. 如果缓冲区仍然已满,则中止
  4. 在套接字上发送1个或更多字节(忽略发送的数量)
  5. 如果出现错误(包括发送决定它会在早期检查时阻止),请获取其值。否则,获取随机错误值
  6. 根据获得的值,可能会在屏幕上打印某些内容
  7. 返回0或1,具体取决于错误值。
  8. 对这些观点的评论:

    1. 为什么write_flags是全球性的?
    2. 真的打算阻止这个功能吗?
    3. 这可能很好
    4. 当然你关心发送了多少数据?
    5. 如果send成功
    6. ,我在文档中看不到任何暗示该值为零的内容

      如果你清除了这个函数的实际意图,那么确保这个函数实际上实现了这个意图可能要容易得多。

      那说

        

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

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

      如果您需要在将来的某个时间发送数据,那么您的解决方案将缺少一个非常关键的部分:您需要为每个保存需要发送的数据的套接字创建缓冲区,然后您定期需要调用一个检查套接字的函数来尝试尽可能地编写它。如果为后一个目的生成一个新线程,这就是select非常有用的东西,因为你可以创建新的线程块,直到它能够写出一些东西。但是,如果您没有生成新线程并且只是定期从主线程调用一个函数来检查,那么您不需要打扰。 (只需将所有内容写入所有内容,即使它是零字节)

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

      编辑:正如另一个答案暗示的那样,这是操作系统擅长的东西。如果您发现套接字缓冲区已满,则不要尝试编写自己的代码来管理它,而是使系统缓冲区更大。如果他们仍然填满,你应该真的认真考虑你的程序需要阻止的想法,以便它停止发送数据比其他更快结束可以处理它。即只需对所有数据使用普通阻止send

答案 1 :(得分:1)

一些一般性建议:

  • 请记住,您正在增加数据。因此,如果您获得1 MB / s,则输出N MB / s与N个客户端。你确定你的网卡可以接受吗?使用较小的数据包会变得更糟,您会获得更多的一般开销。您可能需要考虑广播。

  • 您正在使用非阻塞套接字,但是当它们不空闲时您会阻塞。如果您想要非阻塞,最好在套接字未就绪时立即丢弃数据包。

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

  • while (!sent)循环是无用的,可能是错误的。由于您只检查一个套接字FD_ISSET将始终为真。在FD_ISSET

  • 之后再次检查FD_CLR是错误的
  • 请记住,您的操作系统有一些内部缓冲区用于套接字,并且有方法可以扩展它们(在Linux上不容易,但要获得大值,您需要以root身份进行一些配置)。 / p>

  • 有些套接字库可能比你在合理的时间内实现的更好(boost::asiozmq对于我所知道的那些。

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

编辑:

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

答案 2 :(得分:1)

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

您需要选择合理的I / O策略。这是一个:设置所有套接字非阻塞。当您需要将数据发送到套接字时,只需致电write即可。如果所有数据写入,可爱。如果没有,请保存以后未发送的数据部分,并将套接字添加到写入集。如果您还有其他事可做,请致电select。如果您在写入集中的任何套接字上受到命中,请从您保存的内容中尽可能多地写入字节。如果您编写所有这些,请从写集中删除该套接字。

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

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

答案 3 :(得分:1)

在致电select()之前,您正在呼叫send()。反过来做。仅在select()报告send()时调用WSAEWOULDBLOCK,例如:

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