我有一个C程序尽可能快地发送数据,它使用sendto()方法从发送方发送到接收方,接收端使用recvfrom()方法接收数据。数据被封装到第二层以太网帧中,应用程序将以太网帧直接写到线路上(没有TCP、UDP甚至IP)。这是在x86_64 Linux上的(开发机器只是股票Ubuntu14.04)。我不打算移植到任何其他操作系统,应用程序设计范围是为Linux设计的,所以其他OSes并不重要。
发件人:
while (true)
{
sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0,
(struct sockaddr*)&socket_address, sizeof socket_address);
}接收机:
while (true)
{
recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
}我希望发送方能够检查接收到的数据包;如果接收方应用程序退出,它会将数据发送回发送方,说“我退出”,这样发送方将停止发送数据。我在发送方上使用了轮询()来检查接收到的消息,如下所示,但这大大降低了传输速度,从大约1 1Gbps (968 10Mbps )降低到大约10 10Mbps。我正在测试两台带有1Gbps网卡的PC机之间的交叉电缆。发送者统计发送的帧和帧的大小,接收者统计接收到的帧和帧的大小,所以要确认的是,应用程序实际上是以足够近的线速率接收到的,我不只是看NIC的使用情况或类似的。
Poll()方法:
int rv;
struct pollfd ufds[1];
ufds[0].fd = sockFD;
ufds[0].events = POLLIN
while (true)
{
sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
// wait for events on the sockets, 1ms timeout
rv = poll(ufds, 1, 1);
if (rv > 0) {
if (ufds[0].revents & POLLIN) {
recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
}
}
}1毫秒是可为轮询()方法设置的最低超时。这就是为什么我的传输程序只能以10 10Mbps传输。应用程序可以轻松地使1 1Gbps链路饱和--尽管CPU使用率很低,但正如我前面所说的那样,我得到了968 1Gbps(顺便说一下,我并不是指峰值,即持续吞吐量)。
我删除了轮询()调用,并使用下面的示例切换到select(),但是,在这里使用最小的延迟,我的传输应用程序只能获得175 my。不接近最初的968 close;
Select()方法:
fd_set readfds;
struct timeval tv;
int rv, n;
FD_ZERO(&readfds);
FD_SET(sockFD, &readfds);
n = sockFD + 1;
while (true)
{
sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
tv.tv_sec = 0;
tv.tv_usec = 000001;
rv = select(n, &readfds, NULL, NULL, &tv);
if (rv > 0) {
if (FD_ISSET(sockFD, &readfds)) {
recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
}
}对于今天的系统来说,这两种方法似乎都太慢了(我的cpu使用率在以上所有测试中都在2%左右)。我希望将这个应用程序移到一些10GigE机器上,并在那里开始测试,但我显然不能使用这两种方法中的任何一种。没有更快的方法可以查到吗?
我认为这些应该是非阻塞的,但是通过要求超时,它们在某种程度上是阻塞的;我已经看到了这条线,但这不是我需要的答案。没有任何方法,我可以简单地调用检查,在那个时候,它被调用,对于等待被读取的数据,然后如果没有等待被读取的数据立即返回吗?
作为一个边节点,我还没有阅读recvfrom方法()来查看延迟在哪里,然后才发布,但我确实尝试了以下操作,因为更改代码只需30秒,结果是最糟糕的结果不到1 1Mbps;
发件人:
while (true)
{
// Continually send a frame then check for a frame, send a frame then check for a frame...
sendResult = sendto(sockFD, txBuffer, fSize+headersLength, 0, (struct sockaddr*)&socket_address, sizeof(socket_address));
recvfrom(sockFD, rxBuffer, fSizeTotal, 0, NULL, NULL);
}发布于 2014-06-04 16:48:15
不需要poll或select阻塞任何一段时间。如果poll或select的select参数设置为零,则两个调用都会立即返回,并显示i/o可用性。这样就消除了计时器和随后的四舍五入。
注意,如果您只监视一个文件描述符,为什么这比简单的非阻塞轮询读取要快得多呢?当多个FDs发挥作用时,我本以为这种方法会带来任何好处,所以您的测试发现了这一点是很有趣的。
发布于 2014-05-28 23:18:13
我根本不会使用非阻塞模式。只需在阻塞模式下指定一个线程即可。这样,您只需要执行一个系统调用:recvfrom(),所以您要将上下文切换到内核中。
发布于 2014-05-29 02:15:33
正如您所想的,您的性能受到影响的原因是您将自己限制在每秒发送不超过1000个数据包。
如果您愿意使用两个线程,那么EJP的回答是最好的选择。如果您真的只想使用单个线程,最好的选择是使用select()或poll()来让您知道在传输队列饱和时是否有需要做的事情。因此,您可以将套接字设置为非阻塞模式,也可以在执行I/O时使用MSG_DONTWAIT标志。收到EAGAIN/EWOULDBLOCK通知时停止执行该I/O操作,然后在select()或poll()中再次执行阻塞等待适当的事件(不要设置超时)。在简化错误处理的伪码中:
writeable = true;
readable = false;
make_nonblock(s);
for (;;) {
if (readable) {
while (recvfrom(s,...) > 0) {
done = done_check();
}
if (done) break;
assert(errno == EAGAIN);
readable = false;
}
if (writeable) {
while (sendto(s,...) > 0) {}
assert(errno == EAGAIN);
writeable = false;
}
poll_socket(s, &readable, &writeable);
}https://stackoverflow.com/questions/23922842
复制相似问题