文章总结: Linux的socketwrite函数行为并非非此即彼,而是动态依赖TCP窗口、拥塞窗口、qdisc队列及网卡Ringbuffer状态。最远可一竿子捅到底通知网卡发送,最近仅将数据拷贝至内核skb挂入发送队列。无论如何数据必先进发送队列以支持TCP重传保障可靠性,后续由ACK事件或定时器驱动发送,超额内存配额则会导致阻塞。 综合评分: 86 文章分类: 网络安全,安全开发
socket编程的write/send函数?
原创
车小胖谈网络 车小胖谈网络
车小胖谈网络
2026年7月6日 16:54 上海
在小说阅读器读本章
去阅读
在linux socket编程中,向对端发送数据是可用write函数,但是对于write函数的调用有点疑惑,在网上查资料看了看,以下两种说法:
(1)write函数只是将数据放到发送缓冲区,然后就返回了,之后的发送是由tcp去发送的,也就是说调用了write函数后数据没有被发送
(2)write函数实现中是层层调用,调用到下层tcp中发送数据后返回
如果是第一种的话,执行完write函数后进程不是就顺序执行下面的指令了吗?系统是从哪里调度的tcp来发送数据的呢?怎么保证执行了write函数后数据一定会被tcp发送呢?
以上说的都对,但如果一定要非此即彼,那就错了。好比盲人摸象,有的盲人摸到大腿,说大象是柱子。有的盲人摸到耳朵,说大象是蒲扇,非常片面。
(1)write函数只是将数据放到发送缓冲区,然后就返回了,之后的发送是由tcp去发送的,也就是说调用了write函数后数据没有被发送?
write函数将用户数据copy到socket send queue, 以sk_buffer格式放置在单向链表的tail。
Write函数的逻辑继续check,发现对方最新的advertised window=0,很显然无法继续处理。于是write函数(sys_call)从内核栈返回用户栈。
或者发现当前处于TCP congestion avoidance,但是当前发送但没有确认(outstanding)total packet size ≥ congestion avoidance的congestion window(cwnd),即超额发送。故也无法继续处理。write函数(sys_call)从内核栈返回用户栈。
可能会很困惑,packet放在socket send queue的tail,谁来管它死活?
网络不是静止的,packet还在双向穿梭。
假设,现在收到对方的ACK,更新了window>0,我方的tcp就会从socket send queue拉取packet来进行发送,可能还不是放在tail的那个packet。但是随着对方的ACK不断到来,放置在tail的packet迟早会被发送出去的。
在congestion avoidance场景也是一样的原理。当对方的ACK不断到来,不断ACK我方已经发送但没有ACK的packet,TCP经过对cwnd的检查,发现可以继续发送了。
有人可能会说,如果一直无法收到对方的ACK,是不是就彻底僵住了?
No!TCP有N多的定时器,比如probe timer,re-transmit timer,每当这些定时器超时,就会超时去探测网络并重传packet。
一旦网络恢复,双向都有packet穿梭,双方的TCP将由packet这些event来驱动。当packet无法双向穿梭,将由timer来驱动双方。
(2)write函数实现中是层层调用,调用到下层tcp中发送数据后返回?
是的,这种情况就是同步(synchronized)发送。
通俗地说,就是write这个函数,经过层层的call chain,跨过TCP、IP、qdisc queue(software queue),NIC ring buffer(hardware queue),将来自skb->data映射到NIC ring buffer 一个TX descriptor,并通过PCIe总线更新(write)网卡NIC的寄存器,用于通知网卡,有新的packet需要NIC拉取并transmit。
一竿子桶到底!
做完以上write函数(sys_call)才从内核栈返回用户栈。
NIC ring buffer(hardware queue)是一块内存,这块内存在物理上是在网卡上还是在操作系统所在主机的系统内存上?
后者,即host的system memory,通过memory map一款内存,用于物理网卡NIC与NIC驱动程序共享。
Write还能再前进一点点?
不能了,这是极限操作!
接下来通信就是异步的了,至于网卡什么时候主动来Ring buffer上搬运packet,就完全是网卡的个人行为了。不受write函数的控制。
一个小栗子
client通过三次握手与server建立tcp连接。此时处于slow start,client通过write一个packet,此时socket send queue,slow start, qdisc queue, NIC ring buffer,没有一个元素会拦截packet,这个packet的write就是上文描述的操作。
但是,如果多个CPU,多个线程都在write同一个网卡,同一个NIC ring buffer,一旦NIC ring buffer full,用户的write就无法一竿子到底了,此时会逗留在qdisc queue上,write会返回用户栈。
逗留在qdisc queue上packet,会有CPU来处理吗?
会的,那个竞争到lock的CPU会继续处理的,一切都是动态的,Ring buffer随着网卡的搬运不断清空更多的Ring buffer descriptor,然后那个CPU就会将qdisc queue里排队的packet dequeue到Ring buffer里去。
如果qdisc queue 也满了,那么用户的packet就会被tail drop。
题主提到了以上2 个case,其实还有一个case。
(3)write函数如果超额使用memory quota,是否会被blocked?
会的,每一个用户都有一个memory quota,一旦所有该用户skb(分配)超出配额,write函数会被blocked,直到对方ACK将发送的skb释放掉更多内存。一旦内存足够write函数的使用,write会继续运行。至于什么时候返回,从哪里返回,要看当前的状态。
总结一下
Write这个函数
最远可以一竿子捅到NIC Ring buffer,写NIC物理寄存器,通知网卡有 new packet is ready to go…
最近可能就是将用户data,copied到kernel的skb里,linked in socket send queue tail。
其他的千百种case,介于以上2者之间,如果有兴趣,可以读Linux Kernel code。
无论是哪种case,都要copied到kernel的skb里,linked in socket send queue tail。
为什么?
Retransmit需要啊。一旦发送失败,TCP还可以retransmit 8次,TCP是一个可靠的协议,可靠就是靠这8次重传来保证的!
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:车小胖谈网络 车小胖谈网络 车小胖谈网络《socket编程的write/send函数?》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。





![[工具推荐]AI驱动的后渗透综合管理平台-LeoAI](/images/random/titlepic/8.jpg)





评论