非常缓慢的套接字读取可能是什么原因?

5
我正在使用阻塞TCP套接字作为客户端和服务器。每当我读取时,我首先使用select检查流上是否有数据可用。我总是一次性读写40个字节。虽然大多数读取只需要几毫秒或更少的时间,但有些读取需要超过半秒钟的时间。在我知道套接字上有数据可用之后。
我还使用了TCP_NODELAY 这是什么原因?
编辑2
我分析了每个发送和接收的数据包的时间戳,并发现只有在客户端尝试在服务器写入下一个对象之前读取对象时才会出现此延迟。例如,服务器写入对象编号x,之后客户端尝试在服务器能够开始写入对象编号x + 1之前读取对象x。这使我怀疑服务器端正在进行某种合并。
编辑
服务器正在侦听3个不同的端口。客户端逐个连接到这些端口中的每一个。
有三个连接:一个从服务器经常向客户端发送一些数据。第二个仅从客户端发送数据到服务器。第三个很少使用,只发送单个字节的数据。我遇到了第一个连接的问题。我使用select()检查该连接上是否有数据可用,然后当我对40个字节进行时间戳读取时,我发现需要大约半秒钟才能完成该读取。
任何关于如何对此进行分析的指针都将非常有帮助
在Linux上使用gcc。


rdrr_server_start(void)
{
  int rr_sd;
  int input_sd;
  int ack_sd;
  int fp_sd;
startTcpServer(&rr_sd, remote_rr_port); startTcpServer(&input_sd, remote_input_port); startTcpServer(&ack_sd, remote_ack_port); startTcpServer(&fp_sd, remote_fp_port);
connFD_rr = getTcpConnection(rr_sd); connFD_input = getTcpConnection(input_sd); connFD_ack= getTcpConnection(ack_sd); connFD_fp=getTcpConnection(fp_sd); }
static int getTcpConnection(int sd) { socklen_t len; struct sockaddr_in clientAddress; len = sizeof(clientAddress); int connFD = accept(sd, (struct sockaddr*) &clientAddress, &len); nodelay(connFD); fflush(stdout); return connFD; }
static void startTcpServer(int *sd, const int port) { *sd= socket(AF_INET, SOCK_STREAM, 0); ASSERT(*sd>0);
//设置套接字选项,以便可以重用端口 int enable = 1; setsockopt(*sd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); }

结构体 sockaddr_in a;使用 memset 函数将其初始化为零,并设置其大小;a.sin_family 设置为 AF_INET;a.sin_port 设置为端口号 port;a.sin_addr.s_addr 设置为 INADDR_ANY(任何地址);使用 bind 函数将 socket 与地址绑定;使用 listen 函数开始监听。

函数 nodelay 的作用是禁止延迟,传入的参数为文件描述符 fd;设置 flag=1;使用 setsockopt 函数设置 SOL_TCP、TCP_NODELAY 选项,禁止 TCP 延迟;

startTcpClient 函数中先分别创建了四个套接字 connFD_rr、connFD_input、connFD_ack、connFD_fp;同样地,创建结构体 sockaddr_in a 并初始化;将 a.sin_port 分别设置为 remote_rr_port、remote_input_port、remote_ack_port、remote_fp_port;将 a.sin_addr.s_addr 设置为远程服务器 IP 地址;使用 connect 函数连接到远程服务器,并使用 ASSERT 函数判断连接是否成功;最后将四个套接字分别传递给函数 nodelay。


我有一种感觉,这个问题与硬件有关... - Jah
也许禁用Nagle算法的TCP_NODELAY选项并不是一个好选择,会导致发送许多短数据段,从而导致每个“逻辑”数据包需要多次往返。此外,在应用程序方面还会涉及大量的系统调用。 - wildplasser
发送方是否使用多个write()来发送一个“逻辑”数据包?也许您应该考虑使用writev()和/或重新启用Nagle。(或者甚至TCP_CORK) - wildplasser
如果你能够处理丢包,UDP 也是一个选项。参考:http://www.unixguide.net/network/socketfaq/2.11.shtml(以及下一页)。顺便说一句,如果你不能忍受(变化的)延迟,就不应该使用网络。 - wildplasser
由于我将客户端和服务器都保留在同一台机器上,因此不能出现网络延迟。我无法处理数据包丢失。 - AnkurVj
显示剩余9条评论
5个回答

1

我会对这行代码持怀疑态度:

ASSERT(setsockopt(fd, SOL_TCP, TCP_NODELAY, &flag, sizeof flag)==0);  

如果您正在运行发布版本,则ASSERT很可能被定义为无效,因此调用实际上不会被执行。setsockopt调用不应该在ASSERT语句中。相反,在assert语句中应该验证返回值(在变量中)。带有副作用的断言通常是不好的。因此,即使这不是问题,也应该进行更改。

哦,我实际上还没有展示我的代码中定义的ASSERT部分。它运行良好。 - AnkurVj
1
这段代码仍然不符合惯例;将具有副作用的代码作为名为ASSERT的宏的参数放置,会让读者感到非常困惑,他们很可能会认为它不会在非调试版本上运行。 - R.. GitHub STOP HELPING ICE
@AnkurVj:即使您认为它没问题,也可以尝试从assert中删除调用。我并不怀疑您的能力,但很少有人第一次就正确编写ASSERT宏。 - Mark Wilkins
好的,也许在我没有展示宏展开的代码片段中使用宏并不是一个好主意。 - AnkurVj

0
一个客户端和多个连接?
一些套接字函数可能会阻塞您的执行(即等待函数结果)。我建议为每个连接在服务器端打开一个新线程,这样它们就不会相互干扰...
但我是在瞎猜;您需要发送一些附加信息...

我在我的问题中添加了一些细节。 - AnkurVj
我不确定你的问题是否出在这里,但你应该重新排列你的startTcpServer/getTcpConnection函数。startTcpServer包含一个调用listen()的功能,它将阻止程序执行,直到建立新的连接。之后,你应该接受新的连接并继续进行,但你又有了另一个调用startTcpServer => 另一个listen...(都在同一个线程上)所以,你的套接字在另一个函数中监听(而不是接受传入的连接)...也许像这样重新排列它们:startTcpServer(); getTcpConnection();startTcpServer(); getTcpConnection(); - Ivica

0

你的陈述仍然令人困惑,即“使用单个客户端进行多个TCP连接”。显然,你有一个在一个端口上监听的单个服务器。现在,如果你有多个连接,这意味着有多个客户端连接到服务器,每个客户端都连接到不同的TCP客户端端口。现在,服务器运行select并响应具有数据的任何客户端(表示客户端在其套接字上发送了一些数据)。现在,如果两个客户端同时发送数据,则服务器只能按顺序处理它们。因此,在服务器完成与第一个客户端的处理之前,第二个客户端将无法得到处理。

Select仅允许服务器监视多个描述符(套接字)并处理可用数据。它不是像并行处理那样进行处理。你需要多个线程或进程来实现并行处理。


不,服务器从同一客户端接受了三个不同端口的连接。理想情况下,每个连接应该完全隔离开来。 - AnkurVj
请问你能否贴出代码,展示创建三个不同的服务器套接字并将其传递到select函数中的过程? - fkl

0

可能与timeout参数有关。

您为select调用的timeout参数设置了什么?

尝试将超时参数更改为较大的值并观察延迟。有时候太小的超时和非常频繁的系统调用实际上会导致吞吐量下降。也许如果您假设稍微大一点的延迟,就可以实现更好的结果。

我怀疑是超时或某些代码错误。


@AnkurVj 为什么?这个超时时间非常短。也许你正在让处理器饥饿。select() 超时通常至少需要几秒钟。你必须每30毫秒做什么事情呢? - user207421
@EJP 在进行阻塞读取之前,我必须在超时后调用一次函数。我不会重复执行此操作,只需执行一次即可。然后我会进行阻塞读取。 - AnkurVj
@AnkurVj,也许你应该在第一次调用“select”时使用较短的超时时间,在后续调用中使用较长的超时时间。使用非常短的TCP_NODELAY轮询可能非常低效。可能正常(非实时)操作系统并未针对此类用例进行优化。 - digital_infinity
效率低下是指带宽使用更多吗?对我来说问题可能不在于效率,而在于延迟。即使服务器已经执行了“write()”,我也不能让客户端等待来自服务器的数据。它必须立即对客户端可用。 - AnkurVj
当您设置TCP_NODELAY操作系统时,没有机会将消息字节分组成更大的数据包,因此必须立即发送小数据包。这是低效的。想象一下,您必须将一些沙子运送到城镇的不同部分。您将一些沙子装入卡车,但在卡车装满之前决定出发。您等待第一次交付的时间较短,但必须多次前往。因此,所有工作需要更长时间。(系统针对大约1500个字节或更多的数据包进行优化) - digital_infinity
显示剩余2条评论

0

您可以尝试使用内核扩展 GROGSOTSOethtool 禁用的 TCP_CORK(CORK'ed 模式):

  • TCP_CORK 标记会话中发送将确保数据不会以部分段发送

  • 禁用 generic-segmentation-offloadgeneric-receive-offloadtcp-segmentation-offload 将确保内核在将数据移动到/从用户空间之前不会引入人为延迟以收集其他 tcp 段


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接