接触过网络开发的人,大抵都知道,上层应用使用send函数发送数据,使用recv来接收数据,而send和recv的实现原理又是怎样的呢?
在前面的几篇文章中,我们有提过,TCP是个可靠的、全双工协议。其流量控制或者拥塞控制依赖于滑动窗口和拥塞窗口的滑动来实现,而这两个窗口的滑动实现则是依赖于TCP中的两个buffer,这两个buffer则是TCP socket在内核中的发送缓冲区(send buffer)和接收缓冲区(recv buffer)。
在本文中,我们首先会简单介绍下TCP中发送缓冲区和接收缓冲区的作用(对于后面理解send和recv非常重要),然后讲解Linux系统下,TCP发送和接收数据是如何实现的。
缓冲区,可以理解为是一个临时缓存。
对于发送端来说,socket将数据拷贝到发送临时缓冲区,就立即返回到应用层去做其他的事情,而剩下的将临时缓冲区的数据通过内核发送到对端,这就是tcp的事。
对于接收端来说,内核将网络中的数据拷贝到缓冲区,等待上层应用读取。
上面有讲,进程在调用send()发送的数据的时候,最简单情况(也是一般情况), 将数据拷贝进入socket的内核发送缓冲区之中,然后send便会立即返回。
换句话说,在应用层调用send()返回之时,数据不一定会发送到对端去(和write写文件有点类似),send()仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中。
TCP socket有两种模式,即阻塞模式和非阻塞模式。
在Linux内核中,有两种方式可以查看tcp缓冲区buffer大小。
1、通过查看/etc/sysctl.ronf下的net.ipv4.tcp_wmem值
2、 通过命令'cat /proc/sys/net/ipv4/tcp_wmem'
cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4194304
从上面可以看出,在笔者所在的服务器上,tcp send缓冲区buffer有3个值,分别是4096 16384 4194304。
我们可以通过程序,来修改当前tcp socket的发送缓冲区大小,需要注意的是,如下的代码修改,只会修改当前特定的socket。
int buffer_len = 10240;
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&buffer_len, buffer_len);
接收缓冲区被TCP用来缓存网络上来的数据,一直保存到应用进程读走为止。
对于TCP,如果应用进程一直没有读取,接收缓冲区满了之后,发生的动作是:收端通知发端,接收窗口关闭(win=0)。这个便是滑动窗口的实现。保证TCP套接口接收缓冲区不会溢出,从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。这就是TCP的流量控制,如果对方无视窗口大小而发出了超过窗口大小的数据,则接收方TCP将丢弃它。
与查看发送缓冲区大小的方式一样,接收缓冲区也是通过如上的两种方式。1、通过查看/etc/sysctl.ronf下的net.ipv4.tcp_rmem值
2、通过命令'cat /proc/sys/net/ipv4/tcp_rmem'
cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 4194304
TCP接收缓冲区buffer有3个值,分别是4096 87380 4194304。
同样的,可以通过如下代码,修改接收缓冲区的大小。
int buffer_len = 10240;
setsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&buffer_len, buffer_len);
为了便于我们理解TCP的整个传输过程,我们先了解下TCP的四层模型以及四层模型在数据传输中的流向。后面我们将从四层模型的角度来分析send和recv函数在每层中都做了什么。
NAME
send, sendto, sendmsg - send a message on a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
DESCRIPTION
The system calls send(), sendto(), and sendmsg() are used to transmit a message to another socket.
当调用该函数时,send函数:1、先比较待发送数据的长度len和套接字sockfd的可用发送缓冲区的长度
如果数据长度len大于发送缓冲区的长度,则分多次发送
如果果len小于或者等于sockfd的缓冲区长度,那么send先检查协议是否正在发送sockfd的发送缓冲中的数据
如果len大于剩余空间大小,send就一直等待协议把s的发送缓冲中的数据发送完
如果len小于剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。需要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。
如果是就等待协议把数据发送完
否则,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较sockfd的发送缓冲区的剩余空间和len
从四层模型的角度来分析send实现。
对于TCP,应用程序在创建socket之后,调用connect()函数,通过socket使客户端和服务端建立连接。然后就可以调用send函数发送数据。
数据在传输层进行处理,以TCP协议为例,其主要有以下功能:
不同的协议有不同的发送函数,TCP调用tcp_sendmsg函数,而UDP则调用的是sock_sendmsg函数。
tcp_sendmsg()的主要工作是传输用户层的数据,将数据放入skb中。然后调用tcp_push()发送,tcp_push函数调用tcp_write_xmit() 函数,依次调用发送函数tcp_transmit_skb将skb封装tcp头之后,回调ip_queue_xmit。
ip_queue_xmit(skb)主要有路由查找校验、封装ip头和ip选项,最后通过ip_local_out发送数据包。
数据链路层在不可靠的物理介质上提供可靠的传输。该层的功能包括:物理地址寻址、数据成帧、流量控制、数据错误检测、重发等。这一层的数据单位称为帧(frame)。
上图为send函数源码的调用逻辑图,对源码有兴趣的话,可以在net/tcp.c找到对应的实现。
NAME
recv, recvfrom, recvmsg - receive a message from a socket
SYNOPSIS
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
DESCRIPTION
The recvfrom() and recvmsg() calls are used to receive messages from a socket, and may be used to receive data on a socket whether or not it is connection-oriented.
If src_addr is not NULL, and the underlying protocol provides the source address, this source address is filled in. When src_addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL. The argument
addrlen is a value-result argument, which the caller should initialize before the call to the size of the buffer associated with src_addr, and modified on return to indicate the actual size of the source address. The returned address is
truncated if the buffer provided is too small; in this case, addrlen will return a value greater than was supplied to the call.
The recv() call is normally used only on a connected socket (see connect(2)) and is identical to recvfrom() with a NULL src_addr argument.
当调用该函数时候:
先检查套接字sockfd的接收缓冲区
如果sockfd接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。
当协议把数据接收完毕,recv函数就把sockft的接收缓冲中的数据copy到buf中,recv函数返回其实际copy的字节数。
如果recv在copy时出错,那么它返回SOCKET_ERROR;
如果recv函数在等待协议接收数据时网络中断了,那么它返回0 。
对方优雅的关闭socket并不影响本地recv的正常接收数据;
如果协议缓冲区内没有数据,recv返回0,指示对方关闭;
如果协议缓冲区有数据,则返回对应数据(可能需要多次recv),在最后一次recv时,返回0,指示对方关闭。
从四层模型的角度来分析recv实现。
当数据包到达机器的物理网卡时会触发一个中断,中断处理程序分配skb_buff数据结构,并将从网卡I/O接收到的数据帧复制到skb_buff缓冲区,并设置skb_buff相应的参数。
然后发出软中断,通知内核接收新的数据帧。进入软中断处理流程,调用net_rx_action函数。进入 netif _receive_skb 处理流程。
netif_receive_skb 根据在全局数组 ptype_all 和 ptype_base 中注册的网络层数据报类型,将数据报发送到不同的网络层协议接收函数(INET域主要是ip_rcv和arp_rcv)。
ip_rcv函数为网络层的入口函数。该函数做的第一件事就是数据校验,然后调用ip_rcv_finish这个函数。
ip_rcv_finish函数会调用ip_route_input函数来更新路由,然后寻找路由,决定消息是发送到本地机器,转发还是丢弃。
如果发送到本机,则调用ip_local_deliver函数,可以进行碎片整理(合并多个包),并调用ip_local_deliver_finish。最后调用下一层接口,包括tcp_v4_rcv(TCP)、udp_rcv(UDP)、icmp_rcv(ICMP)、igmp_rcv(IGMP)。如果需要转发,则进入转发流程,调用dev_queue_xmit,进入链路层处理流程。如果不是发送到本机,应该是转发,调用 ip_forward 进行转发 。
在该层,我们会做一些完整性检查,如果发现问题就丢包。如果是tcp,则调用tcp_v4_do_rcv。
然后sk->sk_state == TCP_ESTABLISHED,调用tcp_rcv_builted,调用 tcp_data_queue 方法将消息放入队列。然后使用 tcp_ofo_queue 方法将消息插入接收到 Queued 。
应用程序调用读取或者 recv 的时候,该调用被映射到 /net/socket.c 中的sys_recv系统调用,然后调用 sock_recvmsg 函数。
TCP 会调用 tcp_recvmsg。该函数从套接字缓冲区复制数据到缓冲区。
上述过程,我们总结下就是: 1、数据帧从外部网络到达网卡 2、网卡把帧DMA到内存Ring Buffer中 3、硬中断通知CPU 4、CPU响应硬中断,简单处理后发憷软中断 5、软中断进程处理软中断,调用网卡驱动注册的pool函数开始收包 6、帧被从Ring Buffer中摘下来,存储到skb中 7、协议层开始处理网络帧,并将处理完成后的数据放入socket的接收缓冲区中
上图为整个网络数据接收的函数调用过程,对月接收端来说,当有数据来的时候,都是通过终端来通知内核,最终通过回调,调用系统函数。
下图是send和recv完整的函数调用过程
在实际应用中,如果发送端是非阻塞发送,由于网络的阻塞或者接收端处理过慢,通常出现的情况是,发送应用程序看起来发送了10k的数据,但是只发送了2k到对端缓存中,还有8k在本机缓存中(未发送或者未得到接收端的确认).那么此时,接收应用程序能够收到的数据为2k.假如接收应用程序调用recv函数获取了1k的数据在处理,在这个瞬间,发生了以下情况之一,双方表现为:
1 . 发送应用程序认为send完了10k数据,关闭了socket:
发送主机作为tcp的主动关闭者,连接将处于FIN_WAIT1的半关闭状态(等待对方的ack),并且,发送缓存中的8k数据并不清除,依然会发送给对端.如果接收应用程序依然在recv,那么它会收到余下的8k数据(这个前题是,接收端会在发送端FIN_WAIT1状态超时前收到余下的8k数据.), 然后得到一个对端socket被关闭的消息(recv返回0).这时,应该进行关闭.
2 . 发送应用程序再次调用send发送8k的数据:
假如发送缓存的空间为20k,那么发送缓存可用空间为20-8=12k,大于请求发送的8k,所以send函数将数据做拷贝后,并立即返回8192;
假如发送缓存的空间为12k,那么此时发送缓存可用空间还有12-8=4k,send()会返回4096,应用程序发现返回的值小于请求发送的大小值后,可以认为缓存区已满,这时必须阻塞(或通过select等待下一次socket可写的信号),如果应用程序不理会,立即再次调用send,那么会得到-1的值, 在linux下表现为errno=EAGAIN.
3.接收应用程序在处理完1k数据后,关闭了socket:
接收主机作为主动关闭者,连接将处于FIN_WAIT1的半关闭状态(等待对方的ack).然后,发送应用程序会收到socket可读的信号(通常是 select调用返回socket可读),但在读取时会发现recv函数返回0,这时应该调用close函数来关闭socket(发送给对方ack);
如果发送应用程序没有处理这个可读的信号,而是在send,那么这要分两种情况来考虑,假如是在发送端收到RST标志之后调用send,send将返回-1,同时errno设为ECONNRESET表示对端网络已断开,但是,也有说法是进程会收到SIGPIPE信号,该信号的默认响应动作是退出进程,如果忽略该信号,那么send是返回-1,errno为EPIPE(未证实);如果是在发送端收到RST标志之前,则send像往常一样工作;
以上说的是非阻塞的send情况,假如send是阻塞调用,并且正好处于阻塞时(例如一次性发送一个巨大的buf,超出了发送缓存),对端socket关闭,那么send将返回成功发送的字节数,如果再次调用send,那么会同上一样.
4 . 交换机或路由器的网络断开:
接收应用程序在处理完已收到的1k数据后,会继续从缓存区读取余下的1k数据,然后就表现为无数据可读的现象,这种情况需要应用程序来处理超时.一般做法是设定一个select等待的最大时间,如果超出这个时间依然没有数据可读,则认为socket已不可用.
发送应用程序会不断的将余下的数据发送到网络上,但始终得不到确认,所以缓存区的可用空间持续为0,这种情况也需要应用程序来处理.
如果不由应用程序来处理这种情况超时的情况,也可以通过tcp协议本身来处理,具体可以查看sysctl项中的: net.ipv4.tcp_keepalive_intvl net.ipv4.tcp_keepalive_probes net.ipv4.tcp_keepalive_time
服务器在循环recv,recv的缓冲区大小为100byte,客户端在循环send,每次send 6byte数据,则recv每次收到的数据可能为6byte,12byte,18byte,这是随机的,编程的时候注意正确的处理。
https://slidetodoc.com/network-applications-user-socket-bsd-sockets-kernel-sock/ https://www.programmersought.com/article/819124112/ https://www.programmersought.com/article/749525105/ https://www.fatalerrors.org/a/0dl00z0.html https://blog.csdn.net/w839687571/article/details/44409355 http://lkml.iu.edu/hypermail/linux/kernel/1405.3/01700.html https://linux-kernel-labs.github.io/refs/heads/master/labs/networking.html https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/ipv4/tcp.c#n1581
END
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/2Bw8cVbQlEwpoSKuMlmb_w
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为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 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。