调用send发送网络数据包一定会立马发送出去吗?

网络子系统 专栏收录该内容
23 篇文章 0 订阅

Linux应用层调用了send发送网络数据,那么按照简单的思维,这个动作会触发网卡发送数据,而现实并不是如此!

socket层

首先对于send来说,分为阻塞发送和非阻塞发送:
(1)阻塞操作
内核会检测发送缓冲区是否存在足够的空间存放用户数据,如果空间足够那么直接拷贝数据到socket send buffer,后续发送动作交给协议栈来支持;如果空间不够那么send操作会阻塞,直到内核发送缓冲区空间足够,再把数据拷贝到发送缓冲区,并最后返回用户空间。

(2)非阻塞操作
对于非阻塞操作来说,当发送缓冲区空间不够时send不会阻塞,而是直接返回-1,errno设置为EAGAIN。

以上可知,send仅仅保证了数据存放到了发送缓冲区,而不能保证一定从网卡发出去,因此在socket这一层,发送就已经变为了异步的操作了。

对于TCP协议来说,阻塞操作是在如下的函数中实现的:

int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
        size_t size)
{
                /*
                 *
                 *
                 *
                 * 查看socket发送缓冲区是否已经满了
                 * 如果发送缓冲区满了,对于阻塞操作需要等待
                 *
                 *
                 *
                */
                if (!sk_stream_memory_free(sk))
                    goto wait_for_sndbuf;
......

wait_for_sndbuf:
            set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
            //沒有空闲内存了,需要触发发送缓冲区立即发送,腾出内存空间
            //TCP_NAGLE_PUSH表示强制发送,和NODELAY是同样的含义
            if (copied)
                tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);

            //等待发送缓冲区空间,该函数会导致sleep睡眠.
            if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
                goto do_error;
......
}

现在我还有另外一个问题,既然send函数并不一定保证数据从网卡出去,那么何时会执行网卡发送动作呢?这个问题在后面一个章节做介绍。

TCP/IP层

如果socket发送缓冲区中空间足够大,send拷贝数据到内核发送缓冲区中,那么实际发送动作是什么时候发生的呢?

实际上send函数并不是仅仅拷贝数据,它会判断缓冲区中的数据量以及当前的协议栈状态,当符合发送条件时就会在send中触发实际的发送动作。

主要的逻辑还是在tcp_sendmsg函数中,我查看了对应的代码,总结了如下几个发送的地方,注意以下所有的发送仅仅是指数据经过协议栈发出,并不代表数据已经被Acked

情况1:
发现发送的数据量已经超过发送窗口的一半时,设置TCP_NAGLE_PUSH标记,会忽略nagle规则强制发送缓冲区中的所有skb。

情况2:
如果当前skb是未发送skb链表的header,那么它肯定会被发送,设置TCP_NAGLE_PUSH标记,会忽略nagle规则强制发送当前的这一个skb,注意仅仅发送一个。

情况3:
如果发送缓冲区已经满了,需要触发立即发送,腾出内存空间,设置了TCP_NAGLE_PUSH标记,表示忽略nagle规则强制发送缓冲区中的所有skb。

情况4:
当send把一次应用写入的数据都已经写入到发送缓冲区后,退出函数之前,会调用一次发送函数,注意这次发送是需要按照NAGLE规则判断是否触发真实发送的,如果满足Nagle条件才会发送,否则依然会保留在发送缓冲区中

Nagle

上面介绍了几种可能在send中触发发送的条件,那么其中有一类是需要经过Nagle规则判断的,那么到底什么样的情况是满足Nagle规则的呢?

关于Nagle算法,为了优化小数据包的发送次数,可以累积上层下发的小数据包到一定的程度,再进行组包发送,对于发送端来说,它的发送时机有如下几个:

1.一个包是大于等于MSS长度
2.所有发送包都已经被确认
3.包含有FIN的包
4.包含TCP_NODELAY标记

这里想到一个问题,假如一直发送小包,按照Nagle算法,实际发送函数结束并不会把小包发送出去,而是等待小包合并为大包后才发送,那么假如一直没有小包过来合并,那么按照上述规则,这个小包是不是永远都不发送了呢?实际上并不是的,而是在TCP接收处理时会做处理,可以看上面的第二个规则:
当所有发送包都已经被确认时,即使当前发送缓冲区中存在小包,那么也不继续等待了,而是直接发送出去。另外如果一直没有接收到ACK,当时间超时也会触发发送动作。


linux source code:linux-3.10.108

  • 1
    点赞
  • 1
    评论
  • 1
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

评论 1 您还未登录,请先 登录 后发表或查看评论
©️2022 CSDN 皮肤主题:猿与汪的秘密 设计师:我叫白小胖 返回首页

打赏作者

程序猿Ricky的日常干货

你的鼓励将是我最大的动力!

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值