如何可靠地使 send(2) 进行短发送?
Posted
技术标签:
【中文标题】如何可靠地使 send(2) 进行短发送?【英文标题】:How can I reliably make send(2) do a short send? 【发布时间】:2020-05-02 13:36:01 【问题描述】:send(2)
需要一个缓冲区和一个缓冲区长度。它可以返回错误,或者成功发送到缓冲区长度大小的某些字节数。在某些情况下,send
发送的字节数会少于请求的字节数(例如https://***.com/a/2618755/939259)。
除了发送大消息并从另一个线程触发信号并希望幸运之外,有没有办法在单元测试中持续触发短发送?
【问题讨论】:
您是否尝试过模拟send
并返回特定的测试值?
用信号中断send()
可能不会这样做。您可能需要通过停止读取过程来填充内核的套接字缓冲区,然后尝试发送超出缓冲区剩余部分容量的内容。
@dbush 我在性能敏感代码中使用 gmock,所以这意味着添加一个类并将我的代码模板化,该代码使用 send 仅用于测试一个非常具体的案例。如果必须,我会这样做,但我希望有其他选择。
我真的没有看到更好的选择。除了模拟之外,您可以自己发送信号(这似乎很难确定)或编写自定义内核模块。
如果您可以在发送端操作套接字,您可以尝试将套接字的发送缓冲区大小减小到小于您要传递给send
的数据长度的值.我从来没有摆弄过它,因此我不确定它是否以及如何精确工作,但在 linux 下,有一个套接字选项 SO_SNDBUF
(记录在 socket(7)
中,您可以使用 setsockopt(2)
设置发送缓冲区大小。但是,我自己从来没有这样做过,因此我不知道这是否像我预期的那样有效,以及它是否有助于解决您的问题。
【参考方案1】:
自己动手吧:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t mysend(int fd, void * buff, size_t len, int flags)
#if WANT_PARTIAL_SEND
len = 1 + urand(len -1);
#endif
return send(fd, buff, len, flags);
【讨论】:
OP 很可能不想要使用不同的(不同名称的)函数(这需要修改要测试的代码)。 好吧,你总是可以绕道send()
本身使用一个自定义函数,该函数在内部调用具有较小缓冲区的原始 send()
。【参考方案2】:
send(2)
默认仅在所有数据成功复制到发送缓冲区时返回。
强制它发送更少字节的可能方法取决于具体情况。
如果你
1.可以访问socket
2. 不想改变链接二进制文件中对send
的所有调用的行为,
然后您可以将套接字设置为非阻塞。然后,调用send
将发送尽可能多的八位字节。发送的八位字节的数量主要取决于您要发送的套接字的发送缓冲区中的可用内存量。
因此,如果你得到了
uint8_t my_data[NUM_BYTES_TO_SEND] = 0; /* We don't care what your buffer actually contains in this example ... */
size_t num_bytes = sizeof(my_data);
send(fd, my_data, num_bytes);
并且希望send
发送的邮件少于num_bytes
,您可以尝试减少套接字fd
的发送缓冲区。
这是否可能,如何实现这可能取决于您的操作系统。
在 Linux 下,您可以尝试通过使用 setsockopt(2)
通过选项 SO_SNDBUF
手动设置缓冲区大小来缩小发送缓冲区,如手册页 `socket(7) 中所述:
uint8_t my_data[NUM_BYTES_TO_SEND] = 0;
size_t num_bytes = sizeof(my_data);
size_t max_bytes_to_send = num_bytes - 1; /* Force to send at most 1 byte less than in our buffer */
/* Set the socket non-blocking - you should check status afterwards */
int status = fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
/* reduce the size of the send buffer below the number of bytes you want to send */
setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &max_bytes_to_send, sizeof (size_t));
...
send(fd, my_data, num_bytes);
/* Possibly restore the old socket state */
您可能还必须摆弄SO_SNDBUFFORCE
选项。
更多信息
设置非阻塞:How do I change a TCP socket to be non-blocking?SO_SNDBUF
等信息:What are SO_SNDBUF and SO_RECVBUF
无论如何,最适合您的方式取决于具体情况。
如果您寻找一个可靠的解决方案来检查您可能甚至无法访问的代码,但动态链接到您的项目,您可能会采用此处建议的其他方法:在您的编译代码中覆盖 send
符号。
另一方面,这将影响代码中对send
的所有 调用(当然,您可以绕过这个问题,例如,通过您的send
替换取决于您可以设置的一些标志)。
如果您可以访问套接字 fd,并且只希望特定的 send
调用受到影响(我猜您就是这种情况,因为您谈到了单元测试,并检查发送的字节数是否少于预期测试可能不是您想要的?),那么缩小发送缓冲区可能是可行的方法。
【讨论】:
send()
将在阻塞模式下全部发送,除非发生中断或错误。
@user207421 好点。您可以通过 SO_NONBLOCK 选项设置套接字非阻塞(请参阅***.com/q/1525050/8219383),不是吗?
当然可以,但是 OP 使用的是非阻塞模式吗?如果不是,那么更改模式只是为了使测试成为可能是不明智的。关键是如果他使用阻塞模式,就没有什么可以测试的了。
当然,正如我在回答中所说的那样,这个解决方案取决于具体情况。 OP 谈论实现单元测试,因此他控制套接字的可能性很高。
这是一个建议,就像这里的所有其他解决方案一样。不会有适用于所有情况的解决方案。如建议的那样遮盖send
函数可能适用于其他情况,但会影响链接程序中的每个发送调用。用于单元测试,例如这将是一个坏主意,因为如果发送到更少的场景只是一个需要测试的案例。摆弄套接字本身比较乏味,但可以专门用于对send
的一次专用调用。【参考方案3】:
如果您将要测试的代码打包到共享库(或符号减弱的静态库)中,那么您的测试可执行文件(与库链接)将能够为两者自身覆盖send
以及它链接的库。
示例(覆盖 write
而不是 send
):
#!/bin/sh -eu
cat > code.c <<EOF
#include <unistd.h>
#include <stdio.h>
ssize_t hw(void)
static const char b[]="hello world\n";
return write(1, b, sizeof(b)-1);
void libfunc(void)
puts(__func__);
hw();
EOF
cat > test.c <<'EOF'
#include <stdio.h>
void libfunc(void);
ssize_t hw(void);
#if TEST
ssize_t hw(void)
puts("override");
return 42;
#endif
int main()
libfunc();
puts("====");
printf("%zu\n", hw());
EOF
gcc code.c -fpic -shared -o libcode.so
gcc test.c $PWD/libcode.so -o real
gcc -DTEST test.c $PWD/libcode.so -o mocked
set -x
./real
./mocked
示例输出:
hello world
hello world
libfunc
====
12
libfunc
override
====
override
42
这掩盖了符号的 libc 实现,虽然有访问覆盖的机制(即 dlopen
和/或 -Wl,--wrap
),但您不需要在单元测试中访问它(如果您确实需要在其他单元测试中,将其他单元测试放在不同的程序中是最简单的)。
【讨论】:
以上是关于如何可靠地使 send(2) 进行短发送?的主要内容,如果未能解决你的问题,请参考以下文章