先上这篇文章的目录。
目录
代码执行send成功后,数据就发出去了吗?
回答这个问题之前,需要了解什么是Socket 缓冲区。
编程的时候,如果要跟某个IP建立连接,我们需要调用操作系统提供的 socket API
。
socket 在操作系统层面,可以理解为一个文件。
我们可以对这个文件进行一些方法操作。
用listen
方法,可以让程序作为服务器监听其他客户端的连接。
用connect
,可以作为客户端连接服务器。
用send
或write
可以发送数据,recv
或read
可以接收数据。
在建立好连接之后,这个 socket 文件就像是远端机器的 "代理人" 一样。比如,如果我们想给远端服务发点什么东西,那就只需要对这个文件执行写操作就行了。
socket_api
那写到了这个文件之后,剩下的发送工作自然就是由操作系统内核来完成了。
既然是写给操作系统,那操作系统就需要提供一个地方给用户写。同理,接收消息也是一样。
这个地方就是 socket 缓冲区。
用户发送消息的时候写给 send buffer(发送缓冲区)
用户接收消息的时候写给 recv buffer(接收缓冲区)
也就是说一个socket ,会带有两个缓冲区,一个用于发送,一个用于接收。因为这是个先进先出的结构,有时候也叫它们发送、接收队列。
一个socket有两个缓冲区
如果想要查看 socket 缓冲区,可以在linux环境下执行 netstat -nt
命令。
# netstat -nt
Active Internet connections (w/o servers)
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 代表接收缓冲区,此时是空的,数据都被应用进程接收干净了。
我们在使用TCP建立连接之后,一般会使用 send 发送数据。
int main(int argc, 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);
return 0;
}
上面是一段伪代码,仅用于展示大概逻辑,我们在建立好连接后,一般会在代码中执行 send
方法。那么此时,消息就会被立刻发到对端机器吗?
答案是不确定!执行 send 之后,数据只是拷贝到了socket 缓冲区。至 于什么时候会发数据,发多少数据,全听操作系统安排。
tcp_sendmsg 逻辑
在用户进程中,程序通过操作 socket 会从用户态进入内核态,而 send方法会将数据一路传到传输层。在识别到是 TCP协议后,会调用 tcp_sendmsg 方法。
// net/ipv4/tcp.c
// 以下省略了大量逻辑
int tcp_sendmsg()
{
// 如果还有可以放数据的空间
if (skb_availroom(skb) > 0) {
// 尝试拷贝待发送数据到发送缓冲区
err = skb_add_data_nocache(sk, skb, from, copy);
}
// 下面是尝试发送的逻辑代码,先省略
}
在 tcp_sendmsg 中, 核心工作就是将待发送的数据组织按照先后顺序放入到发送缓冲区中, 然后根据实际情况(比如拥塞窗口等)判断是否要发数据。如果不发送数据,那么此时直接返回。
前面提到的情况里是,发送缓冲区有足够的空间,可以用于拷贝待发送数据。
这里分两种情况。
首先,socket在创建的时候,是可以设置是阻塞的还是非阻塞的。
int s = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP);
比如通过上面的代码,就可以将 socket
设置为非阻塞 (SOCK_NONBLOCK
)。
当发送缓冲区满了,如果还向socket执行send
send阻塞
EAGAIN
错误信息,意思是 Try again
, 现在缓冲区满了,你也别等了,待会再试一次。send非阻塞
我们可以简单看下源码是怎么实现的。还是回到刚才的 tcp_sendmsg
发送方法中。
int tcp_sendmsg()
{
if (skb_availroom(skb) > 0) {
// ..如果有足够缓冲区就执行balabla
} else {
// 如果发送缓冲区没空间了,那就等到有空间,至于等的方式,分阻塞和非阻塞
if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
goto do_error;
}
}
里面提到的 sk_stream_wait_memory
会根据socket
是否阻塞来决定是一直等等一会就返回。
int sk_stream_wait_memory(struct sock *sk, long *timeo_p)
{
while (1) {
// 非阻塞模式时,会等到超时返回 EAGAIN
if (等待超时))
return -EAGAIN;
// 阻塞等待时,会等到发送缓冲区有足够的空间了,才跳出
if (sk_stream_memory_free(sk) && !vm_wait)
break;
}
return err;
}
接收缓冲区也是类似的情况。
当接收缓冲区为空,如果还向socket执行 recv
recv阻塞
EAGAIN
错误信息。recv非阻塞
下面用一张图汇总一下,方便大家保存面试的时候用哈哈哈。
socket读写缓冲区满了的情况汇总
首先我们要知道,一般正常情况下,发送缓冲区和接收缓冲区 都应该是空的。
如果发送、接收缓冲区长时间非空,说明有数据堆积,这往往是由于一些网络问题或用户应用层问题,导致数据没有正常处理。
那么正常情况下,如果 socket
缓冲区为空,执行 close
。就会触发四次挥手。
TCP四次挥手
这个也是面试老八股文内容了,这里我们只需要关注第一次挥手,发的是 FIN
就够了。
socket close
时,主要的逻辑在 tcp_close()
里实现。
先说结论,关闭过程主要有两种情况:
tcp_send_fin()
开始进行四次挥手过程的第一次挥手。void tcp_close(struct sock *sk, long timeout)
{
// 如果接收缓冲区有数据,那么清空数据
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);
} else if (tcp_close_state(sk)) {
// 正常四次挥手, 发 FIN
tcp_send_fin(sk);
}
// 等待关闭
sk_stream_wait_close(sk, timeout);
}
recvbuf非空
以前以为在这种情况下内核会把发送缓冲区数据清空,然后四次挥手。
但是发现源码并不是这样的。
void tcp_send_fin(struct sock *sk)
{
// 获得发送缓冲区的最后一块数据
struct sk_buff *skb, *tskb = tcp_write_queue_tail(sk);
struct tcp_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()
。而只有在进入了这个方法之后,我们才有可能考虑发送缓冲区是否为空的场景。
sendbuf非空
说完TCP了,我们聊聊UDP。这对好基友,同时都是传输层里的重要协议。既然前面提到TCP有发送、接收缓冲区,那UDP有吗?
以前我以为:
"每个UDP socket都有一个接收缓冲区,没有发送缓冲区,从概念上来说就是只要有数据就发,不管对方是否可以正确接收,所以不缓冲,不需要发送缓冲区。"
后来我发现我错了。
UDP socket 也是 socket,一个socket 就是会有收和发两个缓冲区,跟用什么协议关系不大。
有没有是一回事,用不用又是一回事。
事实上,UDP不仅有发送缓冲区,也用发送缓冲区。
一般正常情况下,会把数据直接拷到发送缓冲区后直接发送。
还有一种情况,是在发送数据的时候,设置一个 MSG_MORE
的标记。
ssize_t send(int sock, const void *buf, size_t len, int flags); // flag 置为 MSG_MORE
大概的意思是告诉内核,待会还有其他更多消息要一起发,先别着急发出去。此时内核就会把这份数据先用发送缓冲区缓存起来,待会应用层说ok了,再一起发。
我们可以看下源码。
int udp_sendmsg()
{
// corkreq 为 true 表示是 MSG_MORE 的方式,仅仅组织报文,不发送;
int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
// 将要发送的数据,按照MTU大小分割,每个片段一个skb;并且这些
// skb会放入到套接字的发送缓冲区中;该函数只是组织数据包,并不执行发送动作。
err = ip_append_data(sk, fl4, getfrag, msg->msg_iov, ulen,
sizeof(struct udphdr), &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都会先把数据放到发送队列中,然后根据实际情况再考虑是不是立刻发送。
而我们大部分情况下,都不会用 MSG_MORE
,也就是来一个数据包就直接发一个数据包。从这个行为上来说,虽然UDP用上了发送缓冲区,但实际上并没有起到"缓冲"的作用。
这篇文章,我也就写了20个小时吧。画图也就画吐了而已,每天早上7点钟爬起来写一个多小时再去上班。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/yImrTDVCsVsbZicj-ncn4Q
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。