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

2023-10-07 07:32

本文主要是介绍调用send发送网络数据包一定会立马发送出去吗?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

这篇关于调用send发送网络数据包一定会立马发送出去吗?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/156971

相关文章

Java调用Python的四种方法小结

《Java调用Python的四种方法小结》在现代开发中,结合不同编程语言的优势往往能达到事半功倍的效果,本文将详细介绍四种在Java中调用Python的方法,并推荐一种最常用且实用的方法,希望对大家有... 目录一、在Java类中直接执行python语句二、在Java中直接调用Python脚本三、使用Run

Python如何调用指定路径的模块

《Python如何调用指定路径的模块》要在Python中调用指定路径的模块,可以使用sys.path.append,importlib.util.spec_from_file_location和exe... 目录一、sys.path.append() 方法1. 方法简介2. 使用示例3. 注意事项二、imp

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

在C#中调用Python代码的两种实现方式

《在C#中调用Python代码的两种实现方式》:本文主要介绍在C#中调用Python代码的两种实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#调用python代码的方式1. 使用 Python.NET2. 使用外部进程调用 Python 脚本总结C#调

SpringBoot使用OkHttp完成高效网络请求详解

《SpringBoot使用OkHttp完成高效网络请求详解》OkHttp是一个高效的HTTP客户端,支持同步和异步请求,且具备自动处理cookie、缓存和连接池等高级功能,下面我们来看看SpringB... 目录一、OkHttp 简介二、在 Spring Boot 中集成 OkHttp三、封装 OkHttp

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用