send()的man page显示MSG_MORE
标志,该标志声明为TCP_CORK
。我有一个围绕send()
的包装函数:
int SocketConnection_Write(SocketConnection *this, void *buf, int len) {
errno = 0;
int sent = send(this->fd, buf, len, MSG_NOSIGNAL);
if (errno == EPIPE || errno == ENOTCONN) {
throw(exc, &SocketConnection_NotConnectedException);
} else if (errno == ECONNRESET) {
throw(exc, &SocketConnection_ConnectionResetException);
} else if (sent != len) {
throw(exc, &SocketConnection_LengthMismatchException);
}
return sent;
}
假设我想使用内核缓冲区,我可以使用TCP_CORK
,只要有必要就启用,然后禁用它来刷新缓冲区。但另一方面,因此需要额外的系统调用。因此,MSG_MORE
的使用似乎更适合我。我只需将上面的send()行更改为:
int sent = send(this->fd, buf, len, MSG_NOSIGNAL | MSG_MORE);
根据lwm.net,如果数据包足够大,它们将自动刷新:
如果应用程序设置了该选项 一个套接字,内核不会发送出去 短包。相反,它会等待 直到有足够的数据显示填充 一个最大大小的数据包,然后发送它。 当TCP_CORK关闭时,任何 剩下的数据将会出现在 丝。
但此部分仅提及TCP_CORK
。现在,刷新MSG_MORE
数据包的正确方法是什么?
我只能想到两种可能性:
MSG_MORE
不幸的是,整个主题的文档很少,我在互联网上找不到多少。
我也想知道如何检查一切是否按预期工作?显然,通过strace
运行服务器不是一种选择。那么最简单的方法是使用netcat
然后查看其strace
输出?或者内核是否会以不同的方式处理通过环回接口传输的流量?
答案 0 :(得分:11)
我已经看过内核源代码,两个假设似乎都是正确的。以下代码是net/ipv4/tcp.c
(2.6.33.1)的摘录。
static inline void tcp_push(struct sock *sk, int flags, int mss_now,
int nonagle)
{
struct tcp_sock *tp = tcp_sk(sk);
if (tcp_send_head(sk)) {
struct sk_buff *skb = tcp_write_queue_tail(sk);
if (!(flags & MSG_MORE) || forced_push(tp))
tcp_mark_push(tp, skb);
tcp_mark_urg(tp, flags, skb);
__tcp_push_pending_frames(sk, mss_now,
(flags & MSG_MORE) ? TCP_NAGLE_CORK : nonagle);
}
}
因此,如果标志不设置,则肯定会刷新挂起的帧。但这只是仅缓冲区不为空的情况:
static ssize_t do_tcp_sendpages(struct sock *sk, struct page **pages, int poffset,
size_t psize, int flags)
{
(...)
ssize_t copied;
(...)
copied = 0;
while (psize > 0) {
(...)
if (forced_push(tp)) {
tcp_mark_push(tp, skb);
__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
} else if (skb == tcp_send_head(sk))
tcp_push_one(sk, mss_now);
continue;
wait_for_sndbuf:
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
if (copied)
tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
goto do_error;
mss_now = tcp_send_mss(sk, &size_goal, flags);
}
out:
if (copied)
tcp_push(sk, flags, mss_now, tp->nonagle);
return copied;
do_error:
if (copied)
goto out;
out_err:
return sk_stream_error(sk, flags, err);
}
while
循环的正文将永远不会被执行,因为psize
不大于0.然后,在out
部分,还有另一个机会,tcp_push()
被调用但是因为copied
仍有默认值,所以它也会失败。
因此,发送长度为0的数据包永远不会导致刷新。
下一个理论是重新申请TCP_CORK
。我们先来看看代码:
static int do_tcp_setsockopt(struct sock *sk, int level,
int optname, char __user *optval, unsigned int optlen)
{
(...)
switch (optname) {
(...)
case TCP_NODELAY:
if (val) {
/* TCP_NODELAY is weaker than TCP_CORK, so that
* this option on corked socket is remembered, but
* it is not activated until cork is cleared.
*
* However, when TCP_NODELAY is set we make
* an explicit push, which overrides even TCP_CORK
* for currently queued segments.
*/
tp->nonagle |= TCP_NAGLE_OFF|TCP_NAGLE_PUSH;
tcp_push_pending_frames(sk);
} else {
tp->nonagle &= ~TCP_NAGLE_OFF;
}
break;
case TCP_CORK:
/* When set indicates to always queue non-full frames.
* Later the user clears this option and we transmit
* any pending partial frames in the queue. This is
* meant to be used alongside sendfile() to get properly
* filled frames when the user (for example) must write
* out headers with a write() call first and then use
* sendfile to send out the data parts.
*
* TCP_CORK can be set together with TCP_NODELAY and it is
* stronger than TCP_NODELAY.
*/
if (val) {
tp->nonagle |= TCP_NAGLE_CORK;
} else {
tp->nonagle &= ~TCP_NAGLE_CORK;
if (tp->nonagle&TCP_NAGLE_OFF)
tp->nonagle |= TCP_NAGLE_PUSH;
tcp_push_pending_frames(sk);
}
break;
(...)
如您所见,有两种方法可以刷新。您可以将TCP_NODELAY
设置为1或将TCP_CORK
设置为0.幸运的是,两者都不会检查该标志是否已设置。因此,我最初计划重新应用TCP_CORK
标志可以优化为禁用它,即使它当前没有设置。
我希望这可以帮助有类似问题的人。
答案 1 :(得分:3)
这是一项很多研究......我能提供的只是这个经验性的帖子:
发送一堆设置了MSG_MORE的数据包,然后是没有MSG_MORE的数据包,整个数据集都会消失。它适用于这样的事情:
for (i=0; i<mg_live.length; i++) {
// [...]
if ((n = pth_send(sock, query, len, MSG_MORE | MSG_NOSIGNAL)) < len) {
printf("error writing to socket (sent %i bytes of %i)\n", n, len);
exit(1);
}
}
}
pth_send(sock, "END\n", 4, MSG_NOSIGNAL);
也就是说,当你一次发送所有数据包,并且有一个明确定义的结束......并且你只使用一个套接字。
如果您尝试在上述循环的中间写入另一个套接字,您可能会发现Linux释放以前保存的数据包。至少那似乎是我现在遇到的麻烦。但它可能是一个简单的解决方案。