我有一个维护“流媒体”套接字列表的程序。这些套接字配置为非阻塞套接字。
目前,我使用了一个列表来存储这些流套接字。我有一些数据需要发送到所有这些流套接字,因此我使用迭代器遍历此流套接字列表并调用下面的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;
}
}
}
答案 0 :(得分:3)
如果您认为完全这个函数应该做什么,那么有一件事会有所帮助。它实际上做的可能不是你想要的,并且有一些不好的功能。
我注意到它的主要特征是:
对这些观点的评论:
write_flags
是全球性的?send
成功如果你清除了这个函数的实际意图,那么确保这个函数实际上实现了这个意图可能要容易得多。
那说
我有一些数据需要发送到所有这些流式套接字
如果您需要在继续之前发送数据,那么使用非阻塞写入是不合适的*,因为您将不得不等到可以写入数据。
如果您需要在将来的某个时间发送数据,那么您的解决方案将缺少一个非常关键的部分:您需要为每个保存需要发送的数据的套接字创建缓冲区,然后您定期需要调用一个检查套接字的函数来尝试尽可能地编写它。如果为后一个目的生成一个新线程,这就是select
非常有用的东西,因为你可以创建新的线程块,直到它能够写出一些东西。但是,如果您没有生成新线程并且只是定期从主线程调用一个函数来检查,那么您不需要打扰。 (只需将所有内容写入所有内容,即使它是零字节)
*:至少,这是一个非常不成熟的优化。有一些边缘情况可以通过智能地使用非阻塞写入来获得更高的性能,但如果您不了解这些边缘情况是什么以及非阻塞写入将如何帮助,那么猜测它不太可能取得好成绩。
编辑:正如另一个答案暗示的那样,这是操作系统擅长的东西。如果您发现套接字缓冲区已满,则不要尝试编写自己的代码来管理它,而是使系统缓冲区更大。如果他们仍然填满,你应该真的认真考虑你的程序需要阻止的想法,以便它停止发送数据比其他更快结束可以处理它。即只需对所有数据使用普通阻止send
。
答案 1 :(得分:1)
一些一般性建议:
请记住,您正在增加数据。因此,如果您获得1 MB / s,则输出N MB / s与N个客户端。你确定你的网卡可以接受吗?使用较小的数据包会变得更糟,您会获得更多的一般开销。您可能需要考虑广播。
您正在使用非阻塞套接字,但是当它们不空闲时您会阻塞。如果您想要非阻塞,最好在套接字未就绪时立即丢弃数据包。
最好是一次“选择”多个套接字。做你正在做的一切但是对于所有可用的套接字。您将写入每个“就绪”套接字,然后在有未准备好的套接字时再次重复。这样,您将继续使用首先可用的套接字,然后有一些机会,繁忙的套接字将变为可用。
while (!sent)
循环是无用的,可能是错误的。由于您只检查一个套接字FD_ISSET
将始终为真。在FD_ISSET
FD_CLR
是错误的
请记住,您的操作系统有一些内部缓冲区用于套接字,并且有方法可以扩展它们(在Linux上不容易,但要获得大值,您需要以root身份进行一些配置)。 / p>
有些套接字库可能比你在合理的时间内实现的更好(boost::asio
和zmq
对于我所知道的那些。
如果您需要自己实现,(例如因为zmq有自己的数据包格式),请考虑使用线程池库。
答案 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;
}