MTU和MSS的区别
MTU是在那一层?MSS在那一层?
MTU是在数据链路层的载荷大小也就是传给网络层的大小,mss是在传输层的载荷大小也就是传给应用层的大小
mss是根据mtu得到的
1、MTU: Maximum Transmit Unit,最大传输单元,即物理接口(数据链路层)提供给其上层(通常是IP层)最大一次传输数据的大小;以普遍使用的以太网接口为例,缺省MTU=1500 Byte,(缺省:系统默认状态)这是以太网接口对IP层的约束,如果IP层有<=1500 byte 需要发送,只需要一个IP包就可以完成发送任务;如果IP层有> 1500 byte 数据需要发送,需要分片才能完成发送,这些分片有一个共同点,即IP Header ID相同。
数据链路层,MTU为1500字节,包括IP首部等开销。一般IP首部为20字节,UDP首部为8字节,数据的有效负荷(payload)部分预留是1500-20-8=1472字节。如果数据部分大于1472字节,(TCP则为1500-20-20=1460)就会出现分片现象。
2、MSS:Maximum Segment Size ,最大报文段长度,TCP提交给IP层最大分段大小,不包含TCP Header和 TCP Option,只包含TCP Payload ,MSS是TCP用来限制application层最大的发送字节数。如果底层物理接口MTU= 1500 byte,则 MSS = 1500- 20(IP Header) -20 (TCP Header) = 1460 byte,如果application 有2000 byte发送,需要两个segment才可以完成发送,第一个TCP segment = 1460,第二个TCP segment = 540。
终端MSS是怎么确定的?
为了达到最佳的传输效能TCP协议在建立连接的时候通常要协商双方的MSS值,这个值TCP协议在实现的时候往往用MTU值代替(需要减去IP数据包包头的大小20Bytes和TCP数据段的包头20Bytes)所以往往MSS为1460。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。
如上图 端口53842 为 A; 端口 80 为 B。
见上图,TCP SYN消息,A 发送给B 的MSS= 1460,B发给A最大MSS为1452 。
以后每次通讯,可以看出发送的数据都是 1452 byte。最终为双方中MSS较小的。
path MTU
上述协商的为收发双方的MTU,为终端MTU,但中间路由过程也会经过多个路由设备也会有各自的MTU(path MTU),这时最终的MTU该怎么确定?
1、不确定path MTU,经过时IP分片
由中间路由设备进行IP数据包分片,最终在接收端整合
一个值得注意的是,在分片的数据中,传输层的首部只会出现在第一个分片中,IP数据报分片后,只有第一片带有传输层首部(UDP或ICMP等),后续分片只有IP首部和应用数据,到了目的地后根据IP首部中的信息在网络层进行重组,这一步骤对上层透明,即传输层根本不知道IP层发生了分片与重组。而TCP报文段的每个分段中都有TCP首部,到了目的地后根据TCP首部的信息在传输层进行重组。
TCP分段仅发生在发送端,这是因为在传输过程中,TCP分段是先被封装成IP数据报,再封装在以太网帧中被链路所传输的,并且在端到端路径上通常不会有工作在三层以上,即传输层的设备,故TCP分段不会发生在传输路径中间的某个设备中,在发送端TCP传输层分段后,在接收端TCP传输层重组。
IP分片不仅会发生在在使用UDP、ICMP等没有分段功能的传输层协议的数据发送方,更还会发生在传输途中,甚至有可能都会发生,这是因为原本的大数据报被分片后很可能会经过不同MTU大小的链路,一旦链路MTU大于当前IP分片大小,则需要在当前转发设备(如路由器)中再次分片,但是各个分片只有到达目的地后才会在其网络层重组,而不是像其他网络协议,在下一跳就要进行重组。
发送端进行TCP分段后就一定不会在IP层进行分片,因为MSS本身就是基于MTU推导而来,TCP层分段满足了MSS限制,也就满足了MTU的物理限制。但在TCP分段发生后仍然可能发生IP分片,这是因为TCP分段仅满足了通信两端的MTU要求,传输路径上如经过MTU值比该MTU值更小的链路,那么在转发分片到该条链路的设备中仍会以更小的MTU值作为依据再次分片。当然如果两个通信主机直连,那么TCP连接协商得到的MTU值(两者网卡MTU较小值)就是端到端的路径MTU值,故发送端只要做了TCP分段,则在整个通信过程中一定不会发生IP分片。
2、确定path MTU,将MSS设置为整个链路中最小的
【网络】MTU和MSS
不确定path MTU,经过时IP分片会有什么问题呢?
特别地,对中途发生分片的数据报而言,即使只丢失其中一片数据也要重传整个数据报(这里既然有重传,说明传输层使用的是具有重传功能的协议,如TCP协议。而UDP协议不可靠,即使丢包了也不在意更不会重传,所以必须在应用层实现可靠通信的逻辑),这是因为IP本身没有重传机制,只能由更高层,比如传输层的TCP协议,来负责重传以确保可靠传输。于是,当来自同一个TCP报文段封装得到的原始IP数据报中的的某一片丢失后,接收端TCP迟迟接受不到完整报文段,它就会认为该报文段(包括全部IP分片)已丢失,TCP之后由于超时会收到三个冗余ACK就会重传整个TCP报文段,该报文段对应于一份IP数据报,可能有多个IP分片,但没有办法单独重传其中某一个数据分片,只能重传整个报文段。
分片或分段发生的根源都在于MTU这一数据链路层限制,由于更靠近数据链路层的IP层在感知MTU方面相比于传输层具备天然的优势,在大小超过MTU的大数据报传输问题出现伊始,IP层分片技术就成为主流解决方案。而分片带来的诸多开销(额外首部、复杂处理逻辑)以及其甚至可能在端到端的传输过程中多次发生在网络转发设备(路由器)的问题,让网络协议设计者们又要费尽心思地在端到端通信过程中避免IP分片。
TCP分段技术被提出后,在一定程度上减少了IP分片,但正如上一节末尾所言,TCP分段仅是在发送端避免了IP分片,但是却不能保证在整个端到端通信路径上不会发生IP分片,因为路径上经常会有MTU值小于该TCP连接协商得到的MTU值的链路,在转发至该段链路之前转发设备仍需分片,所以说TCP分段并不能完全避免IP分片。
那么如何才能彻底避免分片呢?
答案其实不难想到:如果能在TCP连接双方正式通信之前,就能通过某种方式预先知道端到端路径的MTU,即路径中包含的各条链路的MTU最小值(称为路径MTU,Path MTU),这一预先获知路径MTU的过程,称为路径MTU发现(Path MTU Discovery),这样此后每次通信都会基于此MTU推导得到的MSS值在发送方TCP层处执行分段。
路径MTU发现如何实现呢?
大家都记得IP首部中有三个标志位,第一位预留,第二位DF(Don’t Fragment),第三位MF(More Fragments)。其中DF如果为1,意思是这个IP数据报在传输的过程中不能分片,如果此IP数据报大于网络接口的MTU,请直接丢弃,并发送消息告诉源主机已丢包。什么消息呢?ICMP的消息,告诉包因为太大了,因为不能分片所以丢弃了,并告诉源主机请重新发送不超过MTU的数据报,那发什么样的ICMP消息呢?再回顾一下ICMP首部结构,有一个Type字段,还有Code字段,发送Type=3, Code=4, MTU=该接口的MTU值X的消息既可以了。当这个ICMP消息到达IP数据报的源主机,源主机知道原来是IP数据报太大了,那最大可以发送多大的包呢?ICMP消息里有,那就是MTU=丢弃该包的网络接口的MTU值X,于是源主机再次发送不超过MTU=X的数据报就可以避免在传输路径上的IP分片。
注意
路径MTU发现看似完美避免了IP分片的问题,但同时又带来了新的问题:如果ICMP消息最终没能到达源主机怎么办?很显然该IP数据报就被静静丢弃了,TCP连接超时而被断开。ICMP为什么回不来?一般是被防火墙或路由器的访问控制列表(Access Control List, ACL)给无情拒绝了。如果你可以管理并配置这些设备,只要允许ICMP Type=3, Code=4 的消息可以通过即可,否则只有老老实实关闭路径MTU发现功能了,因为至少分片还能通信,而避免分片则彻底无法通信了…
TCP分段与IP分片的区别与联系
TCP分段准确的说应该是TCP载荷分段
IP分片准确的来说应该是数据链路层载荷分片
TCP分段与IP分片的区别与联系
简而言之:
UDP不会分段,就由IP来分片。TCP会分段,当然就不用IP来分了!
网卡收发包系统结构
网卡是工作在链路层和物理层的网络组件,是局域网中连接计算机和传输介质的接口,不仅能实现与局域网传输介质之间的物理连接和电信号匹配,还涉及帧的发送与接收、帧的封装与拆封、帧的差错校验、介质访问控制(以太网使用CSMA/CD协议)、数据的编码与解码以及数据缓存的功能等。(物理层:将要传输的数据变为数字信号,编码传出去)
intel82546:PHY与MAC集成在一起的PCI网卡芯片;
bcm5461:PHY芯片,与之对应的MAC是TSEC;
TSEC:Three Speed Ethernet Controller,三速以太网控制器,TSEC内部有DMA子模块;
DMA:(Direct Memory Access,直接存储器访问) ,它将数据从一个地址空间复制到另外一个地址空间,而无需CPU的参与。
网络传输数据链路层,MAC和PHY芯片
3、收包流程
网线 -> Rj45网口 -> MDI 差分线->bcm5461(PHY芯片进行数模转换) -> MII总线-> TSEC(MAC) -> DMA把网络数据包搬到CPU收包缓存->收够n个包,CPU开始处理
4、网络发包
预先把发送的数据拷贝到一个物理连续的缓冲区里,然后把缓冲区的物理地址传递给网卡,启动网卡传输,网卡就用DMA方式把数据发送出去。发送成功后给出一个中断,表示发送完成。
网络发送和接收数据
Linux网络发送和接收内核缓冲区大小的设置
SO_SNDBUF 发送缓冲区
SO_RCVBUF 接收缓冲区
上图的接收缓冲区和发送缓冲区与下图的socket层的接收发送缓冲区等同 都是下下图中的socket接收和发送队列:
收缓冲区和发送缓冲区本质是应用层调用socket函数创建socket时创建的sk_recive_queue和sk_write_queue,详细参考sk_buff章节
图1—数据从网卡到应用总流程
上图第6步不对,帧不是从ring buffer上取下来的,帧本来就保存在sk_buff数据缓冲区中,这里应该改成从ring buffer中找到下一个要解析的sk_buff数据缓冲区(数据帧)地址
第7步有个容易被误解的地方,数据data被放到socket的接收队列中,容易被误解为拷贝,其实不是,sk_buff数据缓冲区创建完成后就一直是存放数据帧的地方,在6,7过程中只是将sk_buff数据缓冲区从最开始挂在ring
buffer下,转变成挂在sk_recive_queue下了
网络设配器的收发建立连接过程,TCP 三次握手
下图是用户进程端调用函数流程图:
三次握手,客户端和服务端对应调用的函数:
1、客户端第一次握手是用户进程系统调用connect()函数立即发送syn报文(C1)后,其它握手都是在内核线程网络协议栈下完成的了,如下:
tcp_v4_rcv的源码:tcp_v4_rcv源码
发送端的2、3和接收端的1、2、3都是在接收报文的内核处理线程ksoftirqd下调用的,如下:
tcp_v4_rcv
|--tcp_v4_do_rcv
| |--tcp_rcv_established //状态为ESTABLISHED
| |--tcp_rcv_state_process
| |--tcp_v4_conn_request //状态为TCP_LISTEN S1服务端第一次握手,接收syn报文 分配request_sock 将该sock添加进半连接队列inet_csk_reqsk_queue_hash_add(sk,req, TCP_TIMEOUT_INIT);
| | --tcp_v4_send_synack //状态为TCP_NEW_SYN_RECV S2服务端第二次握手,发送syn + ack报文
| |--tcp_rcv_synsent_state_process //TCP_SYN_SENT C2客户端第二次握手,接收syn + ack报文
| | |--tcp_send_ack //状态为ESTABLISHED C3客户端第三次握手,发送ack报文
| | |--sock_def_wakeup
| | --wake_up_interruptible_all
| | --__wake_up
| |--case:TCP_SYN_RECV //服务端进入第三次握手后半程
| |--建立路由,初始化拥塞控制块
| |--tcp_set_state //状态变为TCP_ESTABLISHED
| |--sk_wake_async //唤醒阻塞的服务端用户进程
| --sock_def_wakeup //TCP_SYN_RECV
| --wake_up_interruptible_all
| --__wake_up
|--tcp_check_req //状态为TCP_NEW_SYN_RECV
| 一> tcp_v4_syn_recv sock // S3服务端进入第三次握手前半程,接收ack报文 将新的sock对象添加进accept队列
| 一> tcp_create_openreq_child
| 一> inet_csk_clone_lock
| |一> sk_clone_lock
| |一> inet_sk_set_state //状态变为TCP_SYN_RECV
|--tcp_child_process
| --tcp_rcv_state_process //该函数也出现在tcp_v4_do_rcv下
| |--case:TCP_SYN_RECV // S3服务端进入第三次握手后半程
| |--建立路由,初始化拥塞控制块
| |--tcp_set_state //状态变为TCP_ESTABLISHED
| |--sk_wake_async //唤醒阻塞的服务端用户进程
| --sock_def_wakeup //TCP_SYN_RECV
| --wake_up_interruptible_all
| --__wake_up
用户调用套接字实现三次握手的细节,详细作用以及参考文章:
struct sk_buff<=>skb, struct sock<=>sk,struct skb_share_info<=>shinfo
socket():
网络应用调用Socket API socket (int fa mi ly, int type, int protocol) 创建一个 socket,该调用最终会调用 Linux system call socket() ,并最终调用 Linux Kernel 的 sock_create() 方法。该方法返回被创建好了的那个 socket 的 file descriptor。对于每一个 use rs pace 网络应用创建的 socket,在内核中都有一个对应的 struct socket和 struct sock。其中,struct