【问题标题】:What could be the cause of very slow socket reads?套接字读取速度非常慢的原因可能是什么?
【发布时间】:2013-08-12 18:16:10
【问题描述】:

我正在为我的客户端和服务器使用阻塞 TCP 套接字。每当我阅读时,我首先使用select 检查流中是否有可用的数据。我总是一次读写 40 个字节。虽然大多数读取需要几毫秒或更短的时间,但有些只需要半秒以上。在我知道套接字上有可用的数据之后。

我也在用TCP_NODELAY

可能是什么原因造成的?

编辑 2

我分析了每个发送和接收的数据包的时间戳,发现只有当客户端在服务器写入下一个对象之前尝试读取对象时才会发生这种延迟。例如,服务器写入对象编号 x,然后客户端尝试读取对象 x,然后服务器才能够开始写入对象编号 x+1。这让我怀疑服务器端正在发生某种合并。

编辑

服务器正在侦听 3 个不同的端口。客户端一一连接到这些端口中的每一个。

共有三种连接:一种经常从服务器向客户端发送一些数据。第二个只将数据从客户端发送到服务器。第三个很少用于发送单字节数据。我面临第一次连接的问题。我正在使用 select() 检查该连接上的数据是否可用,然后当我为 40 字节读取添加时间戳时,我发现该读取花费了大约半秒。

任何关于如何分析它的指针都会非常有帮助

在 linux 上使用 gcc。

<pre></pre>

rdrr_server_start(无效) {
int rr_sd; int input_sd; 诠释 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); }

静态 int getTcpConnection(int sd) { socklen_t l en;
结构 sockaddr_in 客户端地址; len = sizeof(clientAddress); int connFD = accept(sd, (struct sockaddr*) &clientAddress, &len); 节点层(connFD); fflush(标准输出); 返回 connFD; }

静态无效 startTcpServer(int *sd, const int 端口) { *sd=套接字(AF_INET,SOCK_STREAM,0); 断言(*sd>0);

// 设置套接字选项,以便端口可以重用 诠释启用 = 1; setsockopt(*sd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));

struct sockaddr_in a; memset(&a,0,sizeof(a)); a.sin_family = AF_INET; a.sin_port = 端口; a.sin_addr.s_addr = INADDR_ANY; int bindResult = bind(*sd, (struct sockaddr *) &a, sizeof(a)); 断言(绑定结果 ==0); 听(*sd,2); } 静态无效节点层(int fd){ 整数标志=1; ASSERT(setsockopt(fd, SOL_TCP, TCP_NODELAY, &flag, sizeof flag)==0); }

startTcpClient() { connFD_rr = 套接字(AF_INET,SOCK_STREAM,0); connFD_input = 套接字(AF_INET,SOCK_STREAM,0); connFD_ack = 套接字(AF_INET,SOCK_STREAM,0); connFD_fp=socket(AF_INET, SOCK_STREAM, 0);

struct sockaddr_in a; memset(&a,0,sizeof(a)); a.sin_family = AF_INET; a.sin_port = remote_rr_port; a.sin_addr.s_addr = inet_addr(remote_server_ip);

int CONNECT_TO_SERVER=connect(connFD_rr, &a, sizeof(a)); ASSERT(CONNECT_TO_SERVER==0) ;

a.sin_port = 远程输入端口; CONNECT_TO_SERVER=connect(connFD_input, &a, sizeof(a)); ASSERT(CONNECT_TO_SERVER==0) ;

a.sin_port = remote_ack_port; CONNECT_TO_SERVER=connect(connFD_ack, &a, sizeof(a)); ASSERT(CONNECT_TO_SERVER==0) ;

a.sin_port = remote_fp_port; CONNECT_TO_SERVER=connect(connFD_fp, &a, sizeof(a)); ASSERT(CONNECT_TO_SERVER==0) ;

nodelay(connFD_rr); 节点层(connFD_input); 节点层(connFD_ack); 节点层(connFD_fp); }

【问题讨论】:

  • 我感觉这个问题与硬件有关...
  • 经常从服务端向客户端发送一些数据的一种,我们说的大小是多少?
  • 也许 TCP_NODELAY(禁用 Nagle)是一个不好的选择,导致要发送许多短段,从而导致每个“逻辑”数据包多次往返。加上应用程序方面的大量系统调用。
  • @wildplasser 即使我write() 一次性完成完整的“逻辑”数据包,也会发生这种情况吗?我不允许客户端和服务器之间有任何延迟,这就是我禁用 Nagle 的原因
  • @tuxuday 正好 40 字节数据,每 10 毫秒 2-5 次

标签: c sockets tcp blocking


【解决方案1】:

我会怀疑这行代码:

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

如果您正在运行发布版本,则 ASSERT 很可能被定义为空,因此实际上不会进行调用。 setsockopt 调用不应出现在 ASSERT 语句中。相反,应该在断言语句中验证返回值(在变量中)。 Asserts with side effects 通常是一件坏事。所以即使这不是问题,也应该改变它。

【讨论】:

  • 哦,我实际上并没有显示我的代码中定义的 ASSERT 部分。它工作正常。
  • 这段代码仍然是反惯用的;将带有副作用的代码作为一个名为 ASSERT 的宏的参数,对于可能认为它不能在非调试版本上运行的读者来说是非常混乱的。
  • @AnkurVj:即使您认为没问题,您也可以尝试从断言中删除调用。并不是我怀疑你的能力,但很少有人第一次正确地编写 ASSERT 宏。
  • 哦,好吧,在代码 sn-ps 中使用宏可能是个坏主意,我没有展示它们扩展成什么
【解决方案2】:

一个客户端和多个连接?

某些套接字函数可能会阻止您的执行(即等待函数的结果)。我建议为每个连接打开一个新线程(在服务器端),这样它们就不会相互干扰......

但我在黑暗中拍摄;你需要发送一些额外的信息...

【讨论】:

  • 我在我的问题中添加了一些细节。
  • 我不确定您的问题是否存在,但您可能应该重新安排您的 startTcpServer/getTcpConnection 函数。 startTcpServer 具有对listen() 的调用,它将阻止程序执行,直到建立新的连接。之后你应该接受新的连接并继续前进,但是你还有另一个调用 startTcpServer => 另一个听...(都在同一个线程上)所以不是接受传入的连接,你的套接字听(但在另一个函数中).. . 也许重新排列它们: startTcpServer(); getTcpConnection(); startTcpServer(); getTcpConnection(); ?
【解决方案3】:

您的陈述仍然令人困惑,即“只有一个客户端的多个 tcp 连接”。显然,您有一台服务器在一个端口上侦听。现在,如果您有多个连接,这意味着有多个客户端连接到服务器,每个客户端都连接到不同的 tcp 客户端端口。现在服务器运行选择并响应任何有数据的客户端(意味着客户端在他的套接字上发送了一些数据)。现在如果两个客户端同时发送数据,服务器只能按顺序处理它们。因此,在服务器完成第一个处理之前,不会处理第二个客户端。

Select 仅允许服务器监视多个描述符(套接字)并处理曾经有可用数据的进程。它不像它并行处理。为此,您需要多个线程或进程。

【讨论】:

  • 实际上不,服务器接受了来自同一个客户端的三个不同端口上的连接。理想情况下,每个连接都应该彼此完全隔离。
  • 您能否粘贴您的代码,该代码显示创建了三个不同的服务器套接字并将其传递给选择?
【解决方案4】:

也许它与timeout 参数有关。

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

尝试将超时参数更改为更大的参数并观察延迟。有时,超时时间太短,而且系统调用通常会扼杀吞吐量。如果您假设更大的延迟,也许您可​​以获得更好的结果,这是可以实现的。

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

【讨论】:

  • 您能否在select 返回后从套接字粘贴调用selectread 的代码?
  • @AnkurVj 为什么?这是一个非常短的暂停。也许你正在挨饿处理器。 select() 超时通常至少为几秒钟。每 30 毫秒必须完成的任务是什么?
  • @EJP 我必须在超时之后调用一次函数,然后才能继续并阻止读取。我不会反复这样做。就一次。然后我做一个阻塞读取
  • @AnkurVj 所以也许你应该在第一次调用select 时使用较短的超时,而在下次调用时使用较长的超时。对 TCP_NODELAY 使用非常短的轮询可能会非常低效。可能正常(非实时)操作系统并未针对此类用例进行优化。
  • 由于带宽使用量增加而效率低下?对我来说,问题可能不是效率,而是延迟。即使服务器已经完成了“write()”,我也不能让客户端等待来自服务器的数据。它必须立即可供客户使用
【解决方案5】:

您可以尝试使用TCP_CORK(CORK'ed 模式)和被ethtool 禁用的内核扩展GROGSOTSO

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

  • 禁用 generic-segmentation-offloadgeneric-receive-offloadtcp-segmentation-offload 将确保内核在将数据移入/移出用户空间之前不会引入人为延迟来收集额外的 tcp 段

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-05-08
    • 1970-01-01
    • 1970-01-01
    • 2014-07-23
    • 2020-10-21
    • 2012-07-15
    • 1970-01-01
    • 2019-05-31
    相关资源
    最近更新 更多