网易首页 > 网易号 > 正文 申请入驻

可靠传输的TCP协议send成功就意味着数据一定发出去了?

0
分享至

本文来自小白debug的原创分享,原题“【修正版】动图图解!代码执行send成功后,数据就发出去了吗?”,下文有修订和排版优化。

1、引言

回复过很多IM初学者关于MobileIMSDK通信层代码的疑问,最基础的问题就是“明明用的是TCP协议,而TCP协议也被称为可靠的通信协议,那为什么TCP代码中明确能知道数据是否发送成功,为什么仍然需要应用层去实现消息应答和重传这种逻辑?”。

要真正讲清楚这个问题,还真不是三言两语能讲的明白。。。

本篇文章我们以TCP协议的网络编程逻辑,从Socket缓冲区的角度去拆解,为什么号称可靠传输的TCP协议,在代码中调用send并成功发出数据,并不意味着这个数据就一定通过物理网络发出去了。

2、系列文章

本文是系列文章中的第21篇,大纲如下:

《不为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)》

《不为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)》

《不为人知的网络编程(三):关闭TCP连接时为什么会TIME_WAIT、CLOSE_WAIT》

《不为人知的网络编程(四):深入研究分析TCP的异常关闭》

《不为人知的网络编程(五):UDP的连接性和负载均衡》

《不为人知的网络编程(六):深入地理解UDP协议并用好它》

《不为人知的网络编程(七):如何让不可靠的UDP变的可靠?》

《不为人知的网络编程(八):从数据传输层深度解密HTTP》

《不为人知的网络编程(九):理论联系实际,全方位深入理解DNS》

《不为人知的网络编程(十):深入操作系统,从内核理解网络包的接收过程(Linux篇)》

《不为人知的网络编程(十一):从底层入手,深度分析TCP连接耗时的秘密》

《不为人知的网络编程(十二):彻底搞懂TCP协议层的KeepAlive保活机制》

《不为人知的网络编程(十三):深入操作系统,彻底搞懂127.0.0.1本机网络通信》

《不为人知的网络编程(十四):拔掉网线再插上,TCP连接还在吗?一文即懂!》

《不为人知的网络编程(十五):深入操作系统,一文搞懂Socket到底是什么》

《不为人知的网络编程(十六):深入分析与解决TCP的RST经典异常问题》

《不为人知的网络编程(十七):冰山之下,一次网络请求背后的技术秘密》

《不为人知的网络编程(十八):UDP比TCP高效?还真不一定!》

《不为人知的网络编程(十九):能Ping通,TCP就一定能连接和通信吗?》

《不为人知的网络编程(二十):网络ping不通到底有多少原因?一文搞明白!》

《不为人知的网络编程(二十一):可靠传输的TCP协议send成功就意味着数据一定发出去了?》(☜ 本文)

3、什么是 socket 缓冲区

编程的时候,如果要跟某个IP建立连接,我们需要调用操作系统提供的 socket API。socket 在操作系统层面,可以理解为一个文件。

我们可以对这个文件进行一些方法操作:

  • 1)用listen方法:可以让程序作为服务器监听其他客户端的连接;
  • 2)用connect:可以作为客户端连接服务器;
  • 3)用send或write:可以发送数据,recv或read可以接收数据。

在建立好连接之后,这个 socket 文件就像是远端机器的 "代理人" 一样。比如,如果我们想给远端服务发点什么东西,那就只需要对这个文件执行写操作就行了。



那写到了这个文件之后,剩下的发送工作自然就是由操作系统内核来完成了。既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。

这个地方就是 socket 缓冲区:

  • 1)用户发送消息的时候写给 send buffer(发送缓冲区);
  • 2)用户接收消息的时候写给 recv buffer(接收缓冲区)。

也就是说:一个socket 会带有两个缓冲区,一个用于发送,一个用于接收(如下图所示)。因为这是个先进先出的结构,有时候也叫它们发送、接收队列。



4、怎么观察 socket 缓冲区

如果想要查看 socket 缓冲区,可以在linux环境下执行 netstat -nt 命令:

# netstat -nt
Active Internet connections (w/oservers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 60 172.22.66.69:22 122.14.220.252:59889 ESTABLISHED

这上面表明了,这里有一个协议(Proto)类型为 TCP 的连接,同时还有本地(Local Address)和远端(Foreign Address)的IP信息,状态(State)是已连接。

还有Send-Q 是发送缓冲区,下面的数字60是指,当前还有60 Byte在发送缓冲区中未发送。而 Recv-Q 代表接收缓冲区, 此时是空的,数据都被应用进程接收干净了。

5、执行 send 发送的字节,会立马发送吗?

我们在使用TCP建立连接之后,一般会使用 send 发送数据:

intmain(intargc,char*argv[])
// 创建socket
sockfd=socket(AF_INET,SOCK_STREAM, 0))
// 建立连接
connect(sockfd, 服务器ip信息,sizeof(server))
// 执行 send 发送消息
send(sockfd,str,sizeof(str),0))
// 关闭 socket
close(sockfd);
return0;

上面是一段伪代码,仅用于展示大概逻辑,我们在建立好连接后,一般会在代码中执行 send 方法。那么此时,消息就会被立刻发到对端机器吗?

答案是不确定!执行 send 之后,数据只是拷贝到了socket 缓冲区。至 什么时候会发数据,发多少数据,全听操作系统安排。

tcp_sendmsg 逻辑:



在用户进程中,程序通过操作 socket 会从用户态进入内核态,而 send方法会将数据一路传到传输层。在识别到是 TCP协议后,会调用 tcp_sendmsg 方法。

// net/ipv4/tcp.c
// 以下省略了大量逻辑
inttcp_sendmsg()
// 如果还有可以放数据的空间
if(skb_availroom(skb) > 0) {
// 尝试拷贝待发送数据到发送缓冲区
err = skb_add_data_nocache(sk, skb, from, copy);
// 下面是尝试发送的逻辑代码,先省略

在 tcp_sendmsg 中, 核心工作就是将待发送的数据组织按照先后顺序放入到发送缓冲区中, 然后根据实际情况(比如拥塞窗口等)判断是否要发数据。如果不发送数据,那么此时直接返回。

6、如果Socket缓冲区满了会怎么办

前面提到的情况里是,发送缓冲区有足够的空间,可以用于拷贝待发送数据。

6.1 如果发送缓冲区空间不足,或者满了,执行发送,会怎么样?

这里分两种情况。

首先:socket在创建的时候,是可以设置是阻塞的还是非阻塞的。

ints = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);

比如通过上面的代码,就可以将 socket 设置为非阻塞 (SOCK_NONBLOCK)。

当发送缓冲区满了,如果还向socket执行send。。。。

1)如果此时 socket 是阻塞的,那么程序会在那干等、死等,直到释放出新的缓存空间,就继续把数据拷进去,然后返回。

send阻塞:



2)如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息,意思是Try again , 现在缓冲区满了,你也别等了,待会再试一次。

send非阻塞:



我们可以简单看下源码是怎么实现的,还是回到刚才的 tcp_sendmsg 发送方法中:

inttcp_sendmsg()
if(skb_availroom(skb) > 0) {
// ..如果有足够缓冲区就执行balabla
}else{
// 如果发送缓冲区没空间了,那就等到有空间,至于等的方式,分阻塞和非阻塞
if((err = sk_stream_wait_memory(sk, &timeo)) != 0)
gotodo_error;

里面提到的sk_stream_wait_memory 会根据socket是否阻塞来决定是一直等等一会就返回。

intsk_stream_wait_memory(structsock *sk,long*timeo_p)
while(1) {
// 非阻塞模式时,会等到超时返回 EAGAIN
if(等待超时))
return-EAGAIN;
// 阻塞等待时,会等到发送缓冲区有足够的空间了,才跳出
if(sk_stream_memory_free(sk) && !vm_wait)
break;
returnerr;

6.2如果接收缓冲区为空,执行 recv 会怎么样?

接收缓冲区也是类似的情况。当接收缓冲区为空,如果还向socket执行 recv。

1)如果此时 socket 是阻塞的,那么程序会在那干等,直到接收缓冲区有数据,就会把数据从接收缓冲区拷贝到用户缓冲区,然后返回。

recv阻塞:



2)如果此时 socket 是非阻塞的,程序就会立刻返回一个 EAGAIN 错误信息。

recv非阻塞:



下面用一张图汇总一下,方便大家保存面试的时候用哈哈哈。

socket读写缓冲区满了的情况汇总:



7、如果Socket缓冲区满了会怎么办?

7.1概述

首先我们要知道,一般正常情况下,发送缓冲区和接收缓冲区都应该是空的。如果发送、接收缓冲区长时间非空,说明有数据堆积,这往往是由于一些网络问题或用户应用层问题,导致数据没有正常处理。

那么正常情况下,如果 socket 缓冲区为空,执行 close。就会触发四次挥手。

TCP四次挥手:



这个也是面试老八股文内容了,这里我们只需要关注第一次挥手,发的是 FIN 就够了。

相关文章可以进一步阅读:

  1. 《理论经典:TCP协议的3次握手与4次挥手过程详解》
  2. 《脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手》

7.2如果接收缓冲区有数据时,执行close了,会怎么样?

socket close 时,主要的逻辑在 tcp_close() 里实现。

先说结论,关闭过程主要有两种情况:

1)如果接收缓冲区还有数据未读,会先把接收缓冲区的数据清空,然后给对端发一个RST;

2)如果接收缓冲区是空的,那么就调用 tcp_send_fin() 开始进行四次挥手过程的第一次挥手。

voidtcp_close(structsock *sk,longtimeout)
// 如果接收缓冲区有数据,那么清空数据
while((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {
u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq -
tcp_hdr(skb)->fin;
data_was_unread += len;
__kfree_skb(skb);

if(data_was_unread) {
// 如果接收缓冲区的数据被清空了,发 RST
tcp_send_active_reset(sk, sk->sk_allocation);
}elseif(tcp_close_state(sk)) {
// 正常四次挥手, 发 FIN
tcp_send_fin(sk);
// 等待关闭
sk_stream_wait_close(sk, timeout);

recvbuf非空:



7.3如果发送缓冲区有数据时,执行close了,会怎么样?

以前以为,这种情况下,内核会把发送缓冲区数据清空,然后四次挥手。

但是发现源码并不是这样的:

voidtcp_send_fin(structsock *sk)
// 获得发送缓冲区的最后一块数据
structsk_buff *skb, *tskb = tcp_write_queue_tail(sk);
structtcp_sock *tp = tcp_sk(sk);
// 如果发送缓冲区还有数据
if(tskb && (tcp_send_head(sk) || sk_under_memory_pressure(sk))) {
TCP_SKB_CB(tskb)->tcp_flags |= TCPHDR_FIN;// 把最后一块数据值为 FIN
TCP_SKB_CB(tskb)->end_seq++;
tp->write_seq++;
}else{
// 发送缓冲区没有数据,就造一个FIN包
// 发送数据
__tcp_push_pending_frames(sk, tcp_current_mss(sk), TCP_NAGLE_OFF);

此时,还有些数据没发出去,内核会把发送缓冲区最后一个数据块拿出来。然后置为 FIN。

socket 缓冲区是个先进先出的队列,这种情况是指内核会等待TCP层安静把发送缓冲区数据都发完,最后再执行 四次挥手的第一次挥手(FIN包)。

有一点需要注意的是,只有在接收缓冲区为空的前提下,我们才有可能走到 tcp_send_fin() 。而只有在进入了这个方法之后,我们才有可能考虑发送缓冲区是否为空的场景。



8、拓展阅读:UDP有缓冲区吗?

8.1UDP也有缓冲区吗

说完TCP了,我们聊聊UDP。这对好基友,同时都是传输层里的重要协议。既然前面提到TCP有发送、接收缓冲区,那UDP有吗?

以前我以为:

"每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。"

后来我发现我错了:UDP socket 也是 socket,一个socket 就是会有收和发两个缓冲区。跟用什么协议关系不大。

有没有是一回事,用不用又是一回事。

8.2UDP不用发送缓冲区?

事实上,UDP不仅有发送缓冲区,也发送缓冲区。

一般正常情况下,会把数据直接拷到发送缓冲区后直接发送。还有一种情况,是在发送数据的时候,设置一个 MSG_MORE 的标记。

ssize_t send(intsock,constvoid*buf,size_tlen,intflags);// flag 置为 MSG_MORE

大概的意思是告诉内核,待会还有其他更多消息要一起发,先别着急发出去。此时内核就会把这份数据先用发送缓冲区缓存起来,待会应用层说ok了,再一起发。

我们可以看下源码:

intudp_sendmsg()
// corkreq 为 true 表示是 MSG_MORE 的方式,仅仅组织报文,不发送;
intcorkreq = up->corkflag || msg->msg_flags&MSG_MORE;
// 将要发送的数据,按照MTU大小分割,每个片段一个skb;并且这些
// skb会放入到套接字的发送缓冲区中;该函数只是组织数据包,并不执行发送动作。
err = ip_append_data(sk, fl4, getfrag, msg->msg_iov, ulen,
sizeof(structudphdr), &ipc, &rt,
corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
// 没有启用 MSG_MORE 特性,那么直接将发送队列中的数据发送给IP。
if(!corkreq)
err = udp_push_pending_frames(sk);

因此,不管是不是 MSG_MORE, IP都会先把数据放到发送队列中,然后根据实际情况再考虑是不是立刻发送。(本文已同步发布于:http://www.52im.net/thread-4868-1-1.html)

而我们大部分情况下,都不会用MSG_MORE,也就是来一个数据包就直接发一个数据包。从这个行为上来说,虽然UDP用上了发送缓冲区,但实际上并没有起到"缓冲"的作用。

9、参考资料

[1]TCP/IP详解-第21章·TCP的超时与重传

[2]快速理解TCP协议一篇就够

[3]假如你来设计TCP协议,会怎么做?

[4]手把手教你写基于TCP的Socket长连接

[5]到底什么是Socket?一文即懂!

[6]我们在读写Socket时,究竟在读写什么?

[7]拔掉网线再插上,TCP连接还在吗?一文即懂!

[8]深入操作系统,一文搞懂Socket到底是什么

[9]为何基于TCP协议的移动端IM仍然需要心跳保活机制?

[10]从客户端的角度来谈谈移动端IM的消息可靠性和送达机制

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
在坦桑尼亚遭遇武装人员索贿,小伙用练功券机智化解,当事人提醒在坦中国公民注意安全

在坦桑尼亚遭遇武装人员索贿,小伙用练功券机智化解,当事人提醒在坦中国公民注意安全

潇湘晨报
2025-11-10 19:10:13
大降温!0℃以下!江苏气象最新预报

大降温!0℃以下!江苏气象最新预报

现代快报
2025-11-11 07:25:06
再次强调以经济建设为中心的背景解读:让历史做评价

再次强调以经济建设为中心的背景解读:让历史做评价

小筑
2025-11-09 10:03:20
“马”字辈个股异动拉升

“马”字辈个股异动拉升

第一财经资讯
2025-11-11 10:30:03
15天内2次挑衅!高市早苗引火烧身,中方亮明底牌,这次绝不手软

15天内2次挑衅!高市早苗引火烧身,中方亮明底牌,这次绝不手软

阿七说史
2025-11-10 10:31:06
宋祖德打假李连杰,真的克隆了一个李连杰?

宋祖德打假李连杰,真的克隆了一个李连杰?

港剧叔
2025-11-11 09:30:05
郭汾阳要养废了!看岳云鹏演唱会,眼神发痴手不老实一个劲亲王惠

郭汾阳要养废了!看岳云鹏演唱会,眼神发痴手不老实一个劲亲王惠

小娱乐悠悠
2025-11-11 11:09:24
A股:今天,突然又砸盘了,让人捧腹大笑

A股:今天,突然又砸盘了,让人捧腹大笑

一只番茄鱼
2025-11-11 11:58:15
罗晋传来喜讯,全网恭喜,终于等到这一天

罗晋传来喜讯,全网恭喜,终于等到这一天

梨花黛娱
2025-11-10 17:37:21
罕见!某上市公司补缴税款7.38亿元!补税5.08亿+滞纳金2.3亿

罕见!某上市公司补缴税款7.38亿元!补税5.08亿+滞纳金2.3亿

新浪财经
2025-11-11 14:03:55
重磅!皇马同意交换交易:利物浦 “水货” 换心仪已久的罗德里戈

重磅!皇马同意交换交易:利物浦 “水货” 换心仪已久的罗德里戈

澜归序
2025-11-10 12:22:19
国行PS5 Pro价格持续降低!消费券+国补4461元入手

国行PS5 Pro价格持续降低!消费券+国补4461元入手

游民星空
2025-11-11 14:11:12
得过流感还要打疫苗吗?疾控专家解惑

得过流感还要打疫苗吗?疾控专家解惑

中国青年报
2025-11-10 18:17:04
NBA11日综述:2场绝杀+4状元爆发,两超巨砍30+,1人打破队史纪录

NBA11日综述:2场绝杀+4状元爆发,两超巨砍30+,1人打破队史纪录

篮球圈里的那些事
2025-11-11 14:33:48
台湾回归还有一种新方案,国民党若同意,解放军或无需动武

台湾回归还有一种新方案,国民党若同意,解放军或无需动武

瞻史
2025-10-31 02:20:52
深圳女子公开寻亲:只要当年我不是被父母遗弃的,有多套房已为他们准备好…

深圳女子公开寻亲:只要当年我不是被父母遗弃的,有多套房已为他们准备好…

广东活动
2025-11-11 12:13:02
我们为什么不愿意在举办奥运会了?事情坏就坏在国际奥委会自身。

我们为什么不愿意在举办奥运会了?事情坏就坏在国际奥委会自身。

百态人间
2025-10-18 11:53:06
河北6地,撤乡设镇!

河北6地,撤乡设镇!

极目新闻
2025-11-11 12:00:36
数学,到底是人类的发现还是发明?看完之后你不会再纠结!

数学,到底是人类的发现还是发明?看完之后你不会再纠结!

宇宙时空
2025-11-06 09:02:26
中国首例五胞胎终于长大了,父亲因劳累去世,母亲直言后悔生下他们

中国首例五胞胎终于长大了,父亲因劳累去世,母亲直言后悔生下他们

等风来育儿联盟
2025-08-01 12:21:35
2025-11-11 15:00:49
即时通讯技术分享
即时通讯技术分享
即时通讯相关技术的研究与分享
467文章数 3621关注度
往期回顾 全部

科技要闻

苹果新品惨败,产线拆光、二代搁浅!

头条要闻

牛弹琴:高市早苗创了纪录 上台20天中方两次强烈抗议

头条要闻

牛弹琴:高市早苗创了纪录 上台20天中方两次强烈抗议

体育要闻

重返诺坎普!梅西:希望有一天能回来

娱乐要闻

何超莲窦骁真的没离婚?

财经要闻

南昌三瑞智能IPO:委外代工模式存疑

汽车要闻

盈利"大考",汽车智能化企业的中场战事

态度原创

教育
数码
手机
房产
公开课

教育要闻

傲慢与偏见英文原版语音课09:你总是偏向伊丽莎白

数码要闻

已经结束使命的苹果iPod迎来第24个诞辰

手机要闻

上市半年,荣耀400系列全球发货量突破600万台

房产要闻

封关倒计时!三亚主城 2.3 万 /㎡+ 即买即住,手慢无!

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版