当前位置: 首页 > news >正文

设计本官方网站案例新乡网站推广

设计本官方网站案例,新乡网站推广,wordpress主机空间,网站策划方案1500字MTU和MSS的区别 MTU和MSS的区别 TCP 的 MTU MSS MTU是在那一层#xff1f;MSS在那一层#xff1f; MTU是在数据链路层的载荷大小也就是传给网络层的大小#xff0c;mss是在传输层的载荷大小也就是传给应用层的大小 mss是根据mtu得到的 1、MTU#xff1a; Maximu…MTU和MSS的区别 MTU和MSS的区别 TCP 的 MTU MSS MTU是在那一层MSS在那一层 MTU是在数据链路层的载荷大小也就是传给网络层的大小mss是在传输层的载荷大小也就是传给应用层的大小 mss是根据mtu得到的 1、MTU Maximum Transmit Unit最大传输单元即物理接口数据链路层提供给其上层通常是IP层最大一次传输数据的大小以普遍使用的以太网接口为例缺省MTU1500 Byte缺省系统默认状态这是以太网接口对IP层的约束如果IP层有1500 byte 需要发送只需要一个IP包就可以完成发送任务如果IP层有 1500 byte 数据需要发送需要分片才能完成发送这些分片有一个共同点即IP Header ID相同。 数据链路层,MTU为1500字节包括IP首部等开销。一般IP首部为20字节UDP首部为8字节数据的有效负荷payload部分预留是1500-20-81472字节。如果数据部分大于1472字节TCP则为1500-20-201460就会出现分片现象。 2、MSSMaximum 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 1460B发给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最小值(称为路径MTUPath 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字段发送Type3, Code4, MTU该接口的MTU值X的消息既可以了。当这个ICMP消息到达IP数据报的源主机源主机知道原来是IP数据报太大了那最大可以发送多大的包呢ICMP消息里有那就是MTU丢弃该包的网络接口的MTU值X于是源主机再次发送不超过MTUX的数据报就可以避免在传输路径上的IP分片。 注意 路径MTU发现看似完美避免了IP分片的问题但同时又带来了新的问题如果ICMP消息最终没能到达源主机怎么办很显然该IP数据报就被静静丢弃了TCP连接超时而被断开。ICMP为什么回不来一般是被防火墙或路由器的访问控制列表(Access Control List, ACL)给无情拒绝了。如果你可以管理并配置这些设备只要允许ICMP Type3, Code4 的消息可以通过即可否则只有老老实实关闭路径MTU发现功能了因为至少分片还能通信而避免分片则彻底无法通信了… TCP分段与IP分片的区别与联系 TCP分段准确的说应该是TCP载荷分段 IP分片准确的来说应该是数据链路层载荷分片 TCP分段与IP分片的区别与联系 简而言之 UDP不会分段就由IP来分片。TCP会分段当然就不用IP来分了 网卡收发包系统结构 网卡是工作在链路层和物理层的网络组件是局域网中连接计算机和传输介质的接口不仅能实现与局域网传输介质之间的物理连接和电信号匹配还涉及帧的发送与接收、帧的封装与拆封、帧的差错校验、介质访问控制以太网使用CSMA/CD协议、数据的编码与解码以及数据缓存的功能等。物理层将要传输的数据变为数字信号编码传出去 intel82546PHY与MAC集成在一起的PCI网卡芯片 bcm5461PHY芯片与之对应的MAC是TSEC TSECThree Speed Ethernet Controller三速以太网控制器TSEC内部有DMA子模块 DMA(Direct Memory Access直接存储器访问) 它将数据从一个地址空间复制到另外一个地址空间而无需CPU的参与。 网络传输数据链路层MAC和PHY芯片 3、收包流程 网线 - Rj45网口 - MDI 差分线-bcm5461(PHY芯片进行数模转换) - MII总线- TSECMAC - 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_buffskb, struct sockskstruct skb_share_infoshinfo 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 sock 有三个队列queue分别是 rx , tx 和 err在 sock 结构被初始化的时候这些缓冲队列也被初始化完成在收据收发过程中每个 queue 中保存要发送或者接受的每个 packet 对应的 Linux 网络栈 sk_buffer 数据结构的实例 skb。应用层处理流程 bind(): 服务端 首先要调用 bind() 将 sock 绑定到某个端口然后调用 listen() 使 sock 处于 被动监听状态然后客户端才可以向服务端发起连接请求。 首先根据 端口号 计算一个 hash 值然后通过这个 hash 值在定位到 inet_bind_hashbucket 某个 slot; 定位到 slot 后遍历指向的链表看这个端口是否已经存在 如果端口不存在就新建一个 inet_bind_bucket 并添加到链表中 如果端口已经存在则要检查是否已经有连接使用了该端口 如果已经有连接使用则报错不考虑端口重用 执行到这里已经有一个 inet_bind_bucket 表示这个端口了接下来只要把调用 bind 函数的 sock 加入到 inet_bind_bucket.owners 即可。 bind()函数的使用 bind()和listen()函数的实现源码 listen() 首先将 sock 状态设置为 TCP_LISTEN 然后根据端口号计算哈希值定位到 listening_hash 某个 slot将 sock 添加进来 最后按类似的方式把 sock 也添加到 lhash2 中。bind()和listen()函数的实现源码 。 accpet() 从「 Accept 队列」取出建立连接的socket对象如果Accept 队列全连接队列一直为空这意味着没有客户端和服务器建立连接那么accept就会一直阻塞。 当客户端一调用connect函数发起连接时如果完成tcp三次握手那么accept函数就会被唤醒会取出一个客户端连接注意是已经建立好的连接然后立即返回。 connect(): 客户端通过调用connect 向服务器发送SYN 包 启动重传timer 完成tcp 三次握手 的第一步操作 根据套接字是否设置成阻塞模式 connect 会执行阻塞操作 等待服务器的SYNACK 、RST 等响应报文。 第一次握手主要流程 1、设置 sock 状态为 SYN_SENT 2、 inet_hash_connect 绑定端口将 sock 加入到 bhash (也就是bind hash 表确保其它请求使用该端口时知道知道该端口被占用)和 ehash 中三次握手-主动方发送SYN 3、构造 SYN 包发送给服务端C1。 sys_connect--inet_stream_connect //TCP--tcp_v4_connect-- inet_hash_connect--tcp_connect //tcp_connect 就是构造一个SYN 报文 并将其发送出去 但是由于TCP 要提供可靠服务 所以发送完之后还需要将该SYN 报文加入到发送队列 并启动SYN 重传定时器 只有等收到对端的AC K 报文后 才能将该报文,从发送队列删除井停止SYN 重传定时器。--tcp_connect_queue_skb //将该SYN 报文加入到发送队列 并启动SYN 重传定时器--tcp_transmit_skb //传递给IP层发送该报文--inet_wait_for_connect 把调用connect 的用户进程加入socket对象的等待队列中 进程阻塞--inet_dgram_connect //UDP--ip4_datagram_connectlinux内核协议栈 connect 源码 Linux内核网络协议栈《connect函数剖析》 connect内核源码 connect函数出错情况 服务端第一次握手S1被动方接收SYN第二次握手S2发送syn ack报文 服务端调用 listen 函数后一直处于 TCP_LISTEN 状态等待客户端的连接请求 tcp_v4_rcv|--__inet_lookup|--tcp_v4_do_rcv| |--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 消息都会进入该tcp_v4_rcv函数。首先在全局的 tcp_hashinfo 中查找 sock然后进行相应的处理。 看下查找函数 __inet_lookup 查找过程是先查 ehash然后再查 inet_listen_hashbucket 。 服务端在接收到 SYN 包的时候还没有可以表示这个连接的 sock 存在 ehash 中所以要继续 inet_listen_hashbucket 中查找。 inet_listen_hashbucket 保存的了服务端调用 listen 函数时插入的 sock它处于 TCP_LISTEN 状态端口号等于 SYN 请求包的目的端口因此这里可以找到这个 sock。 找到以上 sock 后服务端要创建一个 request_sock 对象来表示这个 半连接最后要构建 SYNACK 响应包发送给客户端即向客户端发送 第二次握手。 inet_reqsk_alloc() 函数创建一个 request_sock 对象来表示这个半连接然后将它添加到 ehash(这个就是下一章节说的半连接队列) 中在网上经常会看到说服务器在收到 SYN 请求包后会创建一个 socket 保存到半连接队列但实际上 ehash 是个 hash 表只是它又使用了链表来解决 hash 冲突 此外服务端还要更新一下半连接数我们知道半连接数是针对服务端监听 socket 而言的而该 sock 正是第一步在 inet_listen_hashbucket 中查找到的那个 socket更新值sk.icsk_accept_queue.qlen。 这里还有个地方需要注意 代表这个半连接的 request_sock 的状态是 TCP_NEW_SYN_RECV不是 TCP_SYN_RECV这和以前学到的三次握手状态转换图有点出入。 查阅资料发现这个状态是 Linux 4.4 以后新增的本文主要以理清握手过程为主暂不深究为什么新增这个状态以后再探讨。 服务端第一次第二次握手 客户端第二次握手C2接收syn ack报文客户端第三次握手C3发送ack报文 tcp_v4_rcv|-- tcp_v4_do_rcv|- tcp_rcv_state_process|- tcp_rcv_synsent_state_process //客户端第二次握手C2接收syn ack报文|--tcp_send_ack //状态为ESTABLISHED C3客户端第三次握手发送ack报文|--sock_def_wakeup--wake_up_interruptible_all--__wake_up客户端第二次第三次握手 客户端主动建立连接时发送SYN段后连接的状态变为SYN_SENT。 此时如果收到SYNACK段处理函数为tcp_rcv_state_process()。 (1) 接收到SYNACK 一般情况下会收到服务端的SYNACK处理如下 检查ack_seq是否合法。 如果使用了时间戳选项检查回显的时间戳是否合法。 检查TCP的标志位是否合法。 如果SYNACK是合法的更新sock的各种信息。 把连接的状态设置为TCP_ESTABLISHED唤醒调用connect()的进程。 判断是马上发送ACK还是延迟发送。发送ACK报文 第三次握手报文——发送ACK报文 在一下几种情况下会延迟发送。 1、当前刚好有数据等待发送 2、用户设置了TCP_DEFER_ACCEPT选项 3、禁用快速确认模式可通过TCP_QUICKACK选项设置 不过即使延迟也最多延迟200ms这个通过延迟ACK定时器操作。 (2) 接收到SYN 本端之前发送出一个SYN现在又接收到了一个SYN双方同时向对端发起建立连接的请求。 处理如下 把连接状态置为SYN_RECV。 更新sock的各种信息。 构造和发送SYNACK。 接者对端也会回应SYNACK之后的处理流程和服务器端接收ACK类似可参考之前的blog。 当tcp_rcv_synsent_state_process()的返回值大于0时会导致上层调用函数发送一个被动的RST。 Q那么什么情况下此函数的返回值会大于0 A收到一个ACK段但ack_seq的序号不正确或者回显的时间戳不正确。 tcp_finish_connect()用来完成连接的建立主要做了以下事情 把连接的状态从SYN_SENT置为ESTABLISHED。 根据路由缓存初始化TCP相关的变量。 获取默认的拥塞控制算法。 调整发送缓存和接收缓存的大小。 如果使用了SO_KEEPALIVE选项激活保活定时器。 唤醒此socket等待队列上的进程即调用connect的进程。 如果使用了异步通知则发送SIGIO通知异步通知队列上的进程可写。 服务端进入第三次握手S3接收ack报文 将新的sock对象添加进accept队列 tcp_v4_rcv|--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三次握手-被动方接收ACK 在 上上一章节 我们讨论了被动方接受 SYN 后的逻辑简单回顾一下 收到 SYN 请求包后服务端创建一个 request_sock 对象来表示这个 半连接并添加到 ehash 中管理此时该 sock 的状态为 TCP_NEW_SYN_RECV。然后就向客户端发送了第二次握手包。 现在我们看下服务端接收到第三次握手后的处理。 入口函数 tcp_v4_rcv 老相识了 1、首先查找 sock会在 ehash 中找到代表半连接的 request_sock它处于 TCP_NEW_SYN_RECV 状态 2、根据 request_sock 关联到 inet_listen_hashbucket 监听该端口的 tcp_sock TCP_LISTEN 3、调用子流程 tcp_check_req 对 ACK 包做合法性检查检查通过后创建一个新的 tcp_sock 用来表示和客户端的全连接 4、对新的 tcp_sock 进一步处理。 流程相比之前稍微复杂一些我们分步骤阅读首先进入 tcp_check_req 看下 1、对客户端发来的第二次握手包做合法性检查比如序列号是否正确控制位检查等… 2、检查通过后创建一个全新的 tcp_sock 表示这个连接状态为 TCP_SYN_RECV 3、把这个新的 sock 添加到 inet_bind_bucket 的 owners 链表 和 ehash 中管理将代表半连接的 request_sock 从 ehash 中删除 4、最后将新建的 sock 添加到全连接队列同时更新半连接数和全连接队列值。 syn队列半连接队列和accept队列全连接队列 如何建立连接 tcp_v4_rcv。接受函数比发送函数要复杂得多因为数据接收不仅仅只是接收tcp的三次握手也是在接收函数实现的所以收到数据后要判断当前的状态是否正在建立连接等根据发来的信息考虑状态是否要改变在这里我们仅仅考虑在连接建立后数据的接收。 也就是socket层调用accept()等待客户端连接时会调用网络协议栈tcp_rcvmsg() a对应上一章的socket函数 b对应上一章的connect函数,b-1为绑定端口b-2为发送syn报文C1 1client端发起TCP握手发送syn包 2服务端内核收到包以后先将当前连接的信息插入到网络的SYN队列对应上一章的S1这里的队列严格来说是hash表。但按照队列来理解也没问题。TCP半连接队列是怎么取出信息的? 3插入成功后会返回握手确认SYNACK 对应上一章的S2 c 对应上一章的C2 d 对应上一章的C3 4dclient端如果继续完成TCP握手回复ACK确认对应上一章的C3 5内核会将TCP握手完成的包先将对应的连接信息从SYN队列取出56对应上一章的S3,这里的说法只是为了好理解真正的过程按上一章节来 6将连接信息丢入到ACCEPT队列 7应用层sever通过系统调用accept就能拿到这个连接整个网络套接字连接完成 将复杂的流程简化为上述主要是为了以下分析 对syn队列和accept队列的攻击 1、这里有两个队列必然会有满的情况那如果遇到这种情况内核是怎么处理的呢 1如果SYN队列满了内核就会丢弃连接 2如果ACCEPT队列满了那内核不会继续将SYN队列的连接丢到ACCEPT队列如果SYN队列足够大client端后续收发包就会超时 3如果SYN队列满了就会和1一样丢弃连接 2、如何控制SYN队列和ACCEPT队列的大小 1内核2.2版本之前通过listen的backlog可以设置SYN队列半连接状态SYN_REVD和ACCEPT队列完全连接状态ESTABLISHED的上限 2内核2.2版本以后backlog只是表示ACCEPT队列上限SYN队列的上限可以通过/proc/sys/net/ipv4/tcp_max_syn_backlog设置 3、server端通过accept一直等岂不是会卡住收包的线程 在linux网络编程中我们都会追求高性能accept如果卡住接收线程性能会上不去所以socket编程中就会有阻塞和非阻塞模式。 1阻塞模式下的accept就会卡住当前线程什么事情都干不了 2非阻塞模式下可以通过轮询accept去处理其他的事情如果返回EAGAIN就是ACCEPT队列为空如果返回连接信息就是可以处理当前连接 什么是 SYN 攻击如何避免 SYN 攻击 如何查看 TCP 的连接状态 已建立连接的TCP收到SYN会发生什么 SYN 报文什么时候情况下会被丢弃 以上问题在以下参考链接中 TCP的三次握手 网络设配器的收包流程 网络设配器的收包流程 网络收包流程与该文章中有冲突的以本文为主 Linux TCP滑动窗口代码简述 TCP/IP 协议栈在 Linux 内核中的 运行时序分析 TCP输入 之 tcp_rcv_established TCP协议ESTABLISHED状态处理 linux内核TCP/IP源码浅析 1、网卡驱动申请 Rx descriptor ring本质是一致性 DMA 内存保存了若干的 descriptor。将 Rx descriptor ring 的总线地址写入网卡寄存器 RDBA。 2、在数据帧到达网卡时网卡会产生中断网卡驱动为每个 descriptor 分配 skb_buff 数据缓存区(调用skb dev_alloc_skb(pkt_len5))本质上是在内存中分配的一片缓冲区用来接收数据帧。将数据缓存区的总线地址保存到 descriptor。中断环境下 SKB 的分配流程 3、网卡接收到高低电信号。 4、PHY 芯片首先进行数模转换即将电信号转换为比特流。 5、MAC 芯片再将比特流转换为数据帧Frame。 6、网卡驱动将数据帧写入 Rx FIFO。 7、网卡驱动找到 Rx descriptor ring 中下一个将要使用的 descriptor。 8、网卡驱动使用 DMA 通过 PCI 总线将 Rx FIFO 中的数据包复制到 descriptor 保存的总线地址指向的数据缓存区中。其实就是复制到 skb_buff 数据实体中。 9、因为是 DMA 写入所以内核并没有监控数据帧的写入情况。所以在复制完后需要由网卡驱动启动硬中断通知 CPU 数据缓存区中已经有新的数据帧了。每一个硬件中断会对应一个中断号CPU 执行硬下述中断函数。实际上硬中断的中断处理程序最终是通过调用网卡驱动程序来完成的。硬中断触发的驱动程序首先会暂时禁用网卡硬中断意思是告诉网卡再来新的数据就先不要触发硬中断了只需要把数据帧通过 DMA 拷入主存即可。 10、硬中断后继续启动软中断启用软中断目的是将数据帧的后续处理流程交给软中断处理程序异步的慢慢处理。此时网卡驱动就退出硬件中断了其他外设可以继续调用操作系统的硬件中断。但网络 I/O 相关的硬中断需要等到软中断处理完成并再次开启硬中断后才能被再次触发。ksoftirqd 执行软中断函数 net_rx_action() NAPI以 e1000 网卡为例触发 napi() 系统调用napi() 逐一消耗 Rx Ring Buffer 指向的 skb_buff 中的数据包 net_rx_action() - e1000_clean() - e1000_clean_rx_irq() - e1000_receive_skb() - netif_receive_skb() 图2–ksoftirqd 执行软中断函数将 sk_buff 上送到协议栈 该图显示的过程是上述图1里5-6过程的细节。ksoftirqd 内核线程处理软中断 11、内核线程调用网卡驱动通过 netif_receive_skb() 将 sk_buff 上送到协议栈。 表项descriptor会在对应的 sk_buff 送到协议栈后更新 12、重新开启网络 I/O 硬件中断(这里是CPU的硬中断不是网卡的中断)有新的数据帧到来时可以继续触发网络 I/O 硬件中断继续通知 CPU 来消耗数据帧。协议栈处理skb接着往下走同时2-11又开始运行所以图二有两个相同的内核线程来分别执行 协议栈涉及到哪些处理函数详细参考网络协议栈处理 __netif_receive_skb_core 为抓包软件的抓包点 接下来的13,14,15协议栈中 也都属于软中断的处理过程 13、判断skb包的协议接下来去到哪些处理函数里 netif_receive_skb 函数会根据包的协议假如是 udp 包会将包依次送到 ip_rcv (),udp_rcv () 协议处理函数中进行处理。如果是 arp 包的话会进入到 arp_rcv 14、网络层 在ip_rcv函数中网络层取出IP头判断网络包下一步的走向是转发还是交给上层。当确认网络包是要发送给本机后就取出上层协议的类型比如TCP或UDP去掉IP头然后交给传输层处理。 15、传输层 传输层的协议栈如下 tcp_v4_rcv|--tcp_v4_do_rcv| |--tcp_rcv_established //状态为ESTABLISHED| --fast path| --len tcp_header_len //接收的报文无payload数据| --tcp_ack //若接收的报文无payload数据则处理输入ack| --tcp_data_snd_check //检查本端是否有数据要发送并检查发送缓冲区大小| --len tcp_header_len //接收的报文有payload数据| --tcp_copy_to_iovec //当前应用进程是等待数据的进程 且当前套接字运行在当前用户进程现场当前套接字是否运行在当前用户进程现场如果复制成功要清除prequeue队列中已经复制的数据腾出空间。| --slow path: | --tcp_data_queue //将数据加入sk_receive_queue常规队列中| --tcp_data_snd_check //检查本端是否有数据要发送并检查发送缓冲区大小| --tcp_ack_snd_check //检查是否有ack要发送需要则发送 |--tcp_child_process | --tcp_rcv_state_process //该函数也出现在tcp_v4_do_rcv下| |--case:TCP_ESTABLISHED TCP协议是可靠的、快速传递数据的协议当套接字状态是ESTABLISHED状态表明两端已经建立连接可以互相传送数据了tcp_v4_do_rcv接受到数据后首先检查套接字状态如果是ESTABLISHED就交给tcp_rcv_established函数处理具体数据接受过程。如果是LISTEN就由tcp_v4_hnd_req处理如果是其他状态就由tcp_rcv_stateprocess处理关系图如下。 简单来说就是处理过程根据首部预测字段分为快速路径和慢速路径 在快路中对是有有数据负荷进行不同处理 (1) 若接收的报文无payload数据则处理输入ack释放该skb检查本端是否有数据发送有则发送 (2) 若有数据检查是否当前处理进程上下文并且是期望读取的数据若是则将数据复制到用户空间若不满足直接复制到用户空间的情况或者复制失败则需要将数据段加入到接收队列中加入方式包括合并到已有数据段或者加入队列尾部并唤醒用户进程通知有数据可读 在慢路中会进行更详细的校验然后处理接收到的ack处理紧急数据接收数据段其中数据段可能包含乱序的情况最后进行是否有数据和ack的发送检查 通常网络上大多数报文都会走fast pathack报文的处理tcp_ack()在这个函数中会更新发送窗口使窗口右移这样就会有新的报文可以被发送 传输层取出 TCP 头或者 UDP 头后根据四元组【 源 IP、源端口、目的 IP、目的端口 】找出对应的 Socket并把数据拷贝到 Socket 的接收缓冲区。 传输层 TCP 处理入口在 tcp_v4_rcv 函数位于 linux/net/ipv4/tcp ipv4.c 文件中它会做 TCP header 检查等处理。 调用 _tcp_v4_lookup查找该 package 的 open socket。如果找不到该 package 会被丢弃。接下来检查 socket 和 connection 的状态。 上图显示的流程为接在16步骤后面用户进程被唤醒处在内核态被阻塞的用户进程 网络设配器的发包流程 发包流程参考链接 网络发包流程 tcp_sendmsg代码 Linux网络发送流程概述 TCP/IP 协议栈在 Linux 内核中的 运行时序分析 在应用层调用send()或write()函数后数据有两种情况发出去 (1) 用户进程立即将发送队列(发送缓冲区)的数据发送出去 调用send发送网络数据包一定会立马发送出去吗 这里能立即发送出去需要满足以下情况 用户进程调用send()或write()函数发送的第一个报文且 情况1 发现发送的数据量已经超过发送窗口的一半时设置TCP_NAGLE_PUSH标记会忽略nagle规则强制发送缓冲区中的所有skb。 情况2 如果当前skb是未发送skb链表的header那么它肯定会被发送设置TCP_NAGLE_PUSH标记会忽略nagle规则强制发送当前的这一个skb注意仅仅发送一个。 情况3 如果发送缓冲区已经满了需要触发立即发送腾出内存空间设置了TCP_NAGLE_PUSH标记表示忽略nagle规则强制发送缓冲区中的所有skb。 情况4 当send把一次应用写入的数据都已经写入到发送缓冲区后退出函数之前会调用一次发送函数注意这次发送是需要按照NAGLE规则判断是否触发真实发送的如果满足Nagle条件才会发送否则依然会保留在发送缓冲区中。 (2) 通过内核线程调用tcp_v4_rcv函数实现发送队列剩余数据的发送 详情可以查看tcp_v4_rcv的作用那一章节 tcp_sendmsg----copied tcp_send_rcvq --tcp_push--__tcp_push_pending_frames--tcp_mark_push--forced_push // 检查是否需要立马发送,这里是不需要立马发送--tcp_mark_push--__tcp_push_pending_frames-- // 如果是头部需要立马发送 --tcp_push_one // 如果是头部则发送第一个 tcp_push最终都是通过__tcp_push_pending_frames函数里传入的参数nonagle来决定开不开器nagle算法也就是绝不决定要不要立即发送数据 tcp_push_one是立即发送数据 1、网卡驱动创建 Tx descriptor ring将 Tx descriptor ring 的总线地址写入网卡寄存器 TDBA(这一步在网卡启动时就已经完成)。 2、首先应用程序调用 Socket 发送网络包的接口。这是一个系统调用会从用户态陷入到内核态的套接字层中。 在内核中首先根据fd将真正的Socket找出这个Socket对象中记着各种协议栈的函数地址然后构造struct msghdr对象将用户需要发送的数据全部封装在这个struct msghdr结构体中。 3、传输层 在TCP协议的发送函数tcp_sendmsg中创建内核数据结构sk_bufferalloc_skb() 将struct msghdr结构体中的发送数据拷贝到sk_buffer中。调用tcp_write_queue_tail函数获取Socket发送队列中对尾元素将新创建的sk_buffer添加到Socket发送队列的尾部。 在传输层会为器添加TCP头同时拷贝一个新的 sk_buff 副本 这是因为 sk_buff 在到达网卡发送完成的时候会被释放掉而TCP 协议是支持重传的为确保网络包可靠传输在收到对方的 ACK 之前这个 sk_buff 不能被删除。 4、网络层 在网络层主要会做这些工作选取路由确认下一跳的 IP、填充 IP 头、netfilter 过滤、对超过 MTU 大小的数据包进行分片。处理完这些工作后会交给网络接口层处理。 5、网络接口层 网络接口层会进行物理地址寻址以找到下一跳的 MAC 地址填充帧头和帧尾 6、协议栈通过 dev_queue_xmit() 将 sk_buffer 下送到网卡驱动。 7、网卡驱动将 sk_buff的数据结构地址 放入 Tx descriptor ring更新网卡寄存器 TDT。这一步就是将挂在发送缓冲区sk_write_queue上的sk_buff 重新挂在Tx ring buffer上 8、DMA 感知到 TDT 的改变后找到 Tx descriptor ring 中下一个将要使用的 descriptor。 9、DMA 通过 PCI 总线将 descriptor 的数据缓存区复制到 Tx FIFO。 10、复制完后通过 MAC 芯片将数据包发送出去。 11、发送完后网卡更新网卡寄存器 TDH启动硬中断通知 CPU 释放数据缓存区中的数据包。 网卡设备从FIFO发送队列中取出数据包将其发送到网络当发送完成的时候网卡设备会触发一个硬中断来释放内存主要是释放 sk_buff内存和清理 RingBuffer 内存。最后当收到这个 TCP 报文的 ACK 应答时传输层就会释放原始的 sk_buff。 tcp_v4_rcv的作用 tcp_v4_rcv的源码tcp_v4_rcv源码 linux内核TCP/IP源码浅析 TCP/IP 协议栈在 Linux 内核中的 运行时序分析 三次握手中客户端的后两次和服务端的三次实现 如三次握手那一章节 发送发送缓冲区剩余数据 Linux TCP滑动窗口代码简述 除了用户进程调用send()或write()函数主动发出去的数据剩下不能在一个报文中发出去的数据(拷贝在发送缓冲区中发送窗口未发出去的部分) 都是通过内核线程调用tcp_v4_rcv,如下函数栈 tcp_v4_rcv--tcp_ack() //会更新发送窗口使窗口右移这样就会有新的报文可以被发送--tcp_data_snd_check--tcp_write_xmit //在这个函数中会循环获取Socket发送队列中待发送的sk_buffer然后进行拥塞控制以及滑动窗口的管理。--tcp_transmit_skbtcp_ack()在这个函数中会更新发送窗口使窗口右移这样就会有新的报文可以被发送tcp_data_snd_check(sk);就把这些报文发送出去。 TCP的滑动窗口的流量控制是通过协调发送方和接收方的速度来实现的具体来说就是发送方窗口是由接收方回的ack驱动的也就是说发送方要能持续发送包需要持续接收ack。另一个方面接收方在读取报文后发送ack进行响应循环进行接收。这个过程通过驱动窗口的可持续滑动进而实现了流量控制和提高传输效率。 tcp_transmit_skb函数的作用 1重传数据包tcp_retransmit_skb。 2探测路由最大传送单元数据包。 3发送复位连接数据包 4发送连接请求数据包 5发送回答ACK数据包 6窗口探测数据包 应用层、TCP层、IP层之间接口关系如下图 TCP发送函数tcp_transmit_skb 网络结构须知 1、ring buffer 看了很多文章发现很多人误解了ring buffer, 存储的不是网络数据帧存储是sk_buffer数据结构实体的地址和大小 Ring Buffer是在网卡驱动程序启动时创建和初始化的 网卡驱动会在 RAM 中建立并为例两个环形队列ring buffer又称为 BDBuffer descriptor表一个收Rx、一个发Tx每一个表项称为 descriptor描述符(sk_buff)。descriptor 所存放的内容是由 CPU 决定的一般会存放 descriptor 所指代的 Data buffer实际的数据存储空间的指针、数据长度以及一些标志位。 表项descriptor会在对应的 sk_buff 送到协议栈后更新 2、sk_buff与套接字缓冲区 sk_buff与套接字缓冲区一般是指代同一个意思但严格来说sk_buff是一种数据结构用来管理一个数据区的结构这个结构加上这个数据区合起来叫套接字缓冲区即socket buffer9(skb) 也有说挂载在sk_write_queue和sk_read_queue上的sk_buff叫套接字缓冲区 socket kernel buffer skb 是 Linux 内核网络栈L2 到 L4处理网络包packets所使用的 buffer它的类型是 sk_buffer。简单来说一个 skb 表示 Linux 网络栈中的一个 packetTCP 分段和 IP 分片生产的多个 skb 被一个 skb list 形式来保存。 skb的结构和主要操作 分配skb skb alloc_skb(len, GFP_KERNEL) alloc_skb是net/core/skbuff.c里面定义的用于分配缓冲区的函数。我们已经知道数据缓冲区和缓冲区的描述结构(sk_buff结构)是两种不同的实体这就意味着在分配一个缓冲区时需要分配两块内存(一个是缓冲区一个是缓冲区的描述结构sk_buff)。 与数据缓冲区的区别 skb挂载在sk_recive_queue或sk_write_queue上时skb属于数据缓冲区里的数据如下图是被挂载 sk_buff的组织形式 1、skb 在内核的哪些层次会被使用 2、skb 有哪些重要的成员如何借助于一些辅助函数操作这些成员 3、skb_shared_info 中的 frags 和 frag_list 有什么区别各自的用途是什么 4、怎么基于 skb_shared_info 中的 frags[] 实现磁盘文件到网络的零拷贝 5、怎么基于 skb_shared_info 中的 frags[] 实现TSO 硬件TCP Segment Offload 6、linux 内核和驱动是如何支持 scatter-gather 上述这些问题参考如下文章链接 内核 skb/sk_buff 详解 * skb网络包的流向 IP分片结构 frag_list 主要用在内核协议栈的 IP 分片。目前看来还很少有硬件网络设备支持 frag list 卸载如果硬件支持卸载则设备驱动会对 netdevice feature 或上 NETIF_F_FRAGLIST即 netdev-hw_features | NETIF_F_FRAGLIST。 例如当应用层write 发送一笔 4432 (S1 S2 S3) 字节的packetMTU为1500 则会分为三个skb进行发送。三个skb通过第一个skb 的 frag_list 作为链表的头部后面两个skb 通过 skb-next 指针进行串联。 3、滑动窗口与缓冲区的关系 滑动窗口与缓冲区的关系 滑动窗口在tcp_write_xmit函数运行维护 滑动窗口的移动在tcp_ack函数中进行 详细可以了解有关章节 滑动窗口的实现与维护在tcp_write_xmit()函数中 发送窗口 我们在创建套接字的时候 通过SO SENDBUF 指定 了发送缓冲区的大小 如果设置了大小为2048KB 则Li n ux 在真实创建的时候会设置大小 2048 * 2 4096 因为丨inu × 除了要考虑用户的应用 层数据 还需要考虑| i n ux 自身数据结构的开销一协议头部、指针、非线性内存区域结构等· sk buff 结构中通过sk wmem queued 标识发送缓 冲区已经使用的内存大小 并在发包时检查当前缓冫中 区大小是否小于S O SENDBUF 指定的大小 如果不 满足贝刂阻塞当前线程 进行睡眠 等待发送窗口中有 包被AC K 后触发内存free 的回调函数唤醒后继续尝试 发送 接收窗口 主要分为3 部分 RCV.USER 为积压的已经收到但尚未被用户进程 通过read 等系统调用获取的网络数据包 当用户 进程获取后窗口的左端会向右移动 并触发回调 函数将该数据包的内存free 掉 RCV.WN D 为未使用的 推荐返回给该套接字的 客户端发送方当前剩余的可发送的bytes 数 即 拥塞窗口的大小 第三部分为未使用的 尚未预先内存分配的 并 不计算在拥塞窗口的大小中 |---------- RCV.BUFF ----------------|1 2 3|-RCV.USER-|--- RCV.WND ----|----| ----|------------|------------------|------|----RCV.NXT 滑动窗口最重要的是接收窗口因为TCP报文中通告的win代表的是client或server端的接收窗口告诉对方接下来可以接收的数据大小 一个 TCP 接收缓冲区问题的解析 一个 TCP 发送缓冲区问题的解析 滑动窗口与缓冲区的区别 Socket发送缓冲区接收缓冲区快问快答 关于Linux TCP接收缓存以及接收窗口的一个细节解析 滑动窗口工作原理 TCP滑动窗口原理终于清楚了有抓包实验 TCP 的流量控制 TCP 中采用滑动窗口来进行传输控制滑动窗口的大小意味着接收方还有多大的缓冲区可以用于 接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为 0 时发送方一般不能再发送数据报。滑动窗口是 TCP 中实现诸如 ACK 确认、流量控制、拥塞控制的承载结构。 #mssMaximum segment size(一条数据的最大的数据量) win滑动窗口 1、客户端向服务器发起连接客户端的滑动窗口是4096一次发送的最大数据量是1460(第一次握手) 2、服务器接收连接情况告诉客户端服务器的窗口大小是6144一次发送的最大数据量10243(第二次握手) 3、第三次握手 4、4-9客户端连续给服务器发送了 6k 的数据每次发送 1k 5、第10次服务器告诉客户端发送的6k数据以及接收到存储在缓冲区中缓冲区数据已经处理了 2k窗口大小是 2k 6、第11次服务器告诉客户端发送的 6k 数据以及接收到存储在缓冲区中缓冲区数据已经处理了 4k窗口大小是 4k 7、第12次客户端给服务器发送了 1k 的数据 8、第13次客户端主动请求和服务器断开连接并且给服务器发送了1k的数据 9、第14次服务器回复ACK 8194a:同意断开连接的请求 b:告诉客户端已经接受到方才发的2k的数据 c:滑动窗口2k 10、第15、16次通知客户端滑动窗口的大小 11、第17次,第三次挥手服务器端给客户端发送FIN ,请求断开连接 12、第18次,第四次回收各户端同意了服务器端的断开请求。 说明1-3是三次握手4-9是进行通信第一次和第二次握手时不能带有通信数据因为还没有建立连接第三次握手时可以带通信数据 引入滑动窗口后引入tcp传输中存在的问题解决方法 TCP协议中的窗口机制------滑动窗口详解 死锁状态 1概述 当接收端向发送端发送零窗口报文段后不久接收端的接收缓存又有了一些存储空间于是接收端向发送端发送了Windows size 2的报文段然而这个报文段在传输过程中丢失了。发送端一直等待收到接收端发送的非零窗口的通知而接收端一直等待发送端发送数据这样就死锁了。2解决方法 TCP为每个连接设有一个持续计时器。只要TCP连接的一方收到对方的零窗口通知就启动持续计时器若持续计时器设置的时间到期就发送一个零窗口探测报文段仅携带1字节的数据而对方就在确认这个探测报文段时给出了现在的窗口值。TCP报文段的发送时机传输效率问题 可以用以下三种不同的机制控制TCP报文段的发送时机 1TCP维持一个变量MSS等于最大报文段的长度。只要缓冲区存放的数据达到MSS字节时就组装成了一个TCP报文段发送出去 2由发送方的应用进程指明要发送的报文段即TCP支持推送操作 3发送方的一个计时器期限到了这时就把当前已有的缓存数据装入报文段但长度不能超过MSS发送出去。 Nagle算法控制TCP报文段的发送时机 1主旨避免大量发送小包 2初衷避免发送大量的小包防止小包泛滥于网络在理想情况下对于一个TCP连接而言网络上每次只能有一个小包存在。它更多的是端到端意义上的优化 【CORK算法提高网络利用率理想情况下完全避免发送小包仅仅发送满包以及不得不发的小包】 发送方将第一个数据字节发送出去把后面到达的数据字节缓存起来。当发送方接收对第一个数据字符的确认后再把发送缓存中的所有数据组装成一个报文段再发送出去同时继续对随后到达的数据进行缓存。只有在收到对前一个报文段的确认之后才继续发送下一个报文段。规定一个TCP连接最多只能有一个未被确认的未完成的小分组在该分组的确认到达之前不能发送其它的小分组。当数据到达较快而网络速率较慢时用这样的方法可以明显的减少所用的网络带宽。Nagle算法还规定当达到的数据已经达到发送窗口大小的一半或者已经达到报文段的最大长度时就可以立即发送一个报文段。糊涂窗口综合症接收端糊涂网络上小包泛滥的原因之一 1概述 TCP接收方的缓存已满而交互式的应用进程一次只从接收缓存区中读取1字节这样就使接收缓存空间仅腾出1字节然后向发送方发送确认并把窗口设置为1个字节但发送的数据报为40字节的话然后发送方又发来1字节的数据发送方的IP数据报是41字节接收方发回确认仍将窗口设置为1个字节这样网络效率就会很低2解决办法 a.你糊涂我不糊涂法。即Nagle算法。可让接收方等待一段时间使得或者 接收缓存已有足够的空间容纳一个 最长的报文段或者等到接收方缓存已有一半的空闲空间。只要出现这两种情况接收方就发回确认报文并向发送方通知当前的窗口大小。此外发送方也不要发送太小的报文段而是把数据报文积累为足够大的报文段或达到接收方缓存的空间的一半大小。b.治疗接收端的糊涂其中一种机制是延迟ACK还有其它机制例如不发送小窗口通告等对于接收方而言 延迟ACK可以拖延ACK发送时间进而延迟窗口通告在这段时间内接收窗口有机会进一步放大对于发送方而言 不理会接收端的小窗口通告等于说不马上1发送小包小包有时间积累成大包拥塞窗口 TCP的拥塞处理 – Congestion Handling 网络编程5: TCP滑动窗口 上面我们知道了TCP通过Sliding Window来做流控Flow Control但是TCP觉得这还不够因为Sliding Window需要依赖于连接的发送端和接收端其并不知道网络中间发生了什么。TCP的设计者觉得一个伟大而牛逼的协议仅仅做到流控并不够因为流控只是网络模型4层以上的事TCP的还应该更聪明地知道整个网络上的事。 具体一点我们知道TCP通过一个timer采样了RTT并计算RTO但是如果网络上的延时突然增加那么TCP对这个事做出的应对只有重传数据但是重传会导致网络的负担更重于是会导致更大的延迟以及更多的丢包于是这个情况就会进入恶性循环被不断地放大。试想一下如果一个网络内有成千上万的TCP连接都这么行事那么马上就会形成“网络风暴”TCP这个协议就会拖垮整个网络。这是一个灾难。 所以TCP不能忽略网络上发生的事情而无脑地一个劲地重发数据对网络造成更大的伤害。对此TCP的设计理念是TCP不是一个自私的协议当拥塞发生的时候要做自我牺牲。就像交通阻塞一样每个车都应该把路让出来而不要再去抢路了。 拥塞控制主要是四个算法1慢启动2拥塞避免3拥塞发生4快速恢复。 总的思想是增加一个发送方维护的变量cwnd拥塞窗口, 然后swnd min(cwnd, rwnd). 即发送窗口大小由拥塞窗口和接收窗口中较小的值决定。 4、为什么会粘包怎么拆包 网络包的大小占用 考虑一个包含2bytes 的网络包 需要包括预留头 64 bytes) Mac 头(14bytes) 甲头(20bytes) Tcp 头(32bytes) 有效负载为2bytes(len) skb shared info(320bytes) 452bytes, 向上取 整后为512bytes; sk_buff 这个存储结构占用 256bytes; 则一个2bytes 的网络包需要占用 512256768bytes(truesize) 的内存空间 因此当发送这个网络包时 Case1 不存在缓冲区积压 则新建一个 sk buff 进行网络包的发送 skb-truesize 768 skb-datalen 0 skb_shared_info 结构有效负载 (非线性区域) skb-len 2 有效负载 (线性区域 非线性区域(datalen)这里暂时不考虑协议头部) Case2: 如果缓冲区积压 存在未被AC K 的已经 发送的网络包一即SEND-Q (发送缓冲区sk_write_queue)中存在sk buff 结构 Li n ux 会尝试将当前包合并到SEND一Q 的最后一个sk buff 结构中 粘包 考虑我们上述的768bytes 的结构体为SEN D 一Q 的最后一个sk buff, 当用户进程继续调用write 系统调用写入2kb 的数据时 前一个数据包还未达到 MSS/MTU 的限制、整个缓冲区的大小未达到SO SENDBUF 指定的限制 会进行包的合并packet data 2 2 头部的相关信息都可以 进行复用 因为套接字缓冲区与套接字是一一对应的 tail_skb-truesize 768 tail_skb-datalen 0 tail_skb-len 4 (2 2) 当启用了Nagle算法后数据会倾向于堆积到一定大小或超时后才真正往网络发送数据因此启用Nagle算法后的发送缓冲区更容易发生数据堆积。 在tcp_sendmsg()中有下面这段代码 if (tcp_send_head(sk)) { /* 还有未发送的数据说明该skb还未发送 *//* 如果网卡不支持检验和计算那么skb的最大长度为MSS即不能使用GSO */if (skb-ip_summed CHECKSUM_NONE)max mss_now;copy max - skb-len; /* 此skb可追加的最大数据长度 */}if (copy 0) { /* 需要使用新的skb来装数据 */ 在tcp_write_xmit()函数里下面进行nagle算法测试 /* tso_segs1表示无需tso分段 */if (tso_segs 1) {// 更据上面的代码nagle只有在发送数据小于窗口的时候才有用/* 根据nagle算法计算是否需要发送数据 */if (unlikely(!tcp_nagle_test(tp, skb, mss_now,(tcp_skb_is_last(sk, skb) ?nonagle : TCP_NAGLE_PUSH))))break;}会发生黏包的两种情况 5、期间网络包经历了哪些缓冲区经历了几次拷贝 read then write 常见的场景中 当我们要在网络中发送一个文件 那 么首先需要通过read 系统调用陷入内核态读取 PageCache 通过CPU Copy 数据页到用户态内存 中 接着将数据页封装成对应的应用层协议报文 并 通过write 系统调用陷入内核态将应用层报文CPU Copy 到套接字缓冲区中 经过TC P/I P 处理后形成甲 包 最后通过网卡的DMA Engine 将RingBuffer Tx.ring 中的sk buff 进行DMA Copy 到网卡的内 存中 并将| P 包封装为帧并对外发送。 PS: 如果PageCache 中不存在对应的数据页缓存 贝刂需要通过磁盘D M A opy 到内存中。 因此read then write 需要两次系统调用 4 次上下文 切换 因为系统调用需要将用户态线程切换到内核态 线程进行执行 两次CPU Copy 、两次DMA 发送要拷贝三次 1、用户空间– 内核空间(套接字缓冲区也即sk_write_queue)– 2、网卡驱动空间(也是在内核空间但是是要发送的sk_buff的备份主要是为了重传,在tcp_transmit_skb函数中操作)– 3、 DMA到网卡空间的发送队列 接收只用拷贝两次 1、DMA将网络帧从网卡拷贝到sk_buff缓冲区(这里的sk_buff缓冲区是指申请了有一大堆以sk_buff实体结构和数据结构为单位的内存并挂载在ring buffer上) 2、从sk_buff缓冲区拷贝到用户空间(有的人要问了中间为什么没有拷贝到套接字缓冲区呢因为中间的协议栈只是将sk_buff添加到了套接字缓冲区sk_read_queue上) 6、fastopen获取当前的发送MSS设置TCP控制块结构 在tcp_sendmsg()中实现 TCP/IP协议栈在Linux内核中的运行时序分析 7、超时重传拥塞窗口滑动窗口 在tcp_write_xmit()中实现 8、 TCP segmentip分片 9、 构建TCP头部和校验和 ,确定TCP数据段协议头包含的内容 tcp_transmit_skb() 在ip_fragment ()进行ip分片 10、接收缓冲区被TCP和UDP用来缓存网络上来的数据一直保存到应用进程读走为止。 对于TCP如果应用进程一直没有读取接收缓冲区满了之后发生的动作是接收端通知发发端接收窗口关闭win0。这个便是滑动窗口的实现。保证TCP套接口接收缓冲区不会溢出从而保证了TCP是可靠传输。因为对方不允许发出超过所通告窗口大小的数据。 这就是TCP的流量控制如果对方无视窗口大小而发出了超过窗口大小的数据则接收方TCP将丢弃它。 11、发送端send返回之时数据不一定会发送到对端去 send()仅仅是把应用层buffer的数据拷贝进socket的内核发送buffer中发送是内核线程调用发送网络协议的事情和用户进程send其实没有太大关系。 调用send发送网络数据包一定会立马发送出去吗 代码执行send成功后数据就发出去了吗 Linux网络发送流程概述 TCP/IP 协议栈在 Linux 内核中的 运行时序分析 TCP协议发送函数tcp_sendmsg 12、通过合理的设置“TCP.SO_RCVBUF TCP.SO_SNDBUF”来提高系统的吞吐量以及快速检测tcp链路的连通性 这两个选项就是来设置TCP连接的两个buffer尺寸的。 13、UDP接收缓冲区 每个UDP socket都有一个接收缓冲区没有发送缓冲区从概念上来说就是只要有数据就发不管对方是否可以正确接收所以不缓冲不需要发送缓冲区。 UDP当套接口接收缓冲区满时新来的数据报无法进入接收缓冲区此数据报就被丢弃。UDP是没有流量控制的快的发送者可以很容易地就淹没慢的接收者导致接收方的UDP丢弃数据报。 14、阻塞阻塞的本质是进程因为资源等待而主动让出CPU 进程从运行队列删除幷加入到等待队列然后等待资源。等超时或数据资源到来则唤醒进程继续执行若有数据可读那就把数据拷贝给进程无数据可读但超时了则返回进程继续执行后面的逻辑。 TCP阻塞和非阻塞模式下的数据发送 15、非阻塞本质是应用进程掌控读取数据的节奏通过轮训的方式查询数据是否可读 进程始终占用着CPU能比较好地满足高性能进程需求执行效率高数据没到位进程可以继续处理其他业务无需阻塞其他业务进行。 UDP有没有缓冲区 UDP也有缓冲区吗 3.1 UDP也有缓冲区吗 说完TCP了我们聊聊UDP。这对好基友同时都是传输层里的重要协议。既然前面提到TCP有发送、接收缓冲区那UDP有吗 以前我以为。 “每个UDP socket都有一个接收缓冲区没有发送缓冲区从概念上来说就是只要有数据就发不管对方是否可以正确接收所以不缓冲不需要发送缓冲区。” 后来我发现我错了。 UDP socket 也是 socket一个socket 就是会有收和发两个缓冲区。跟用什么协议关系不大。 有没有是一回事用不用又是一回事。 3.2 UDP不用发送缓冲区 事实上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_flagsMSG_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用上了发送缓冲区但实际上并没有起到缓冲的作用。 深入分析网络接收发送 下图为发送接收的总流程函数图 接收数据 上图为网卡接收数据到IP网络层 网卡接收数据流程——设备驱动与内核线程详细函数解析 图解 Linux 网络包接收过程 TCP/IP协议栈在Linux内核中的运行时序分析(有接收代码详解) 发送数据 对于数据发送网络发送缓冲区与窗口关系的探究与思考有发送代码详解 网卡适配器收发数据帧流程 深度解析Linux网络收发包流程 TCP/IP 协议栈在 Linux 内核中的 运行时序分析 TCP三次握手和四次挥手 wireshark分析TCP的三次握手和四次断开 断开请求FIN可以是client发也可以是服务器端发 下面这个就是服务器段发 下面是我进入路由器界面发送http请求获取到的包如下 最后一个http 报文就是服务器段发起的FIN结束 TCP为什么是三次握手而不是两次或者四次的解析 TCP为什么是三次握手而不是两次或者四次的解析 “已失效的连接请求报文段”的产生在这样一种情况下client发出的第一个连接请求报文段并没有丢失而是在某个网络结点长时间的滞留了以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段同意建立连接。 假设不采用“三次握手”那么只要server发出确认新的连接就建立了。由于现在client并没有发出建立连接的请求因此不会理睬server的确认也不会向server发送数据。但server却以为新的运输连接已经建立并一直等待client发来数据。这样server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况client不会向server的确认发出确认。server由于收不到确认就知道client并没有要求建立连接。 socket套接字编程 python网络编程 网络编程——python
http://www.w-s-a.com/news/323425/

相关文章:

  • 做网站要买服务器吗单页设计思路
  • 一 电子商务网站建设规划网站开发前端框架和后端框架
  • 自助网站建设系统软件自己免费建设网站
  • 百度微建站access如何与网站连接数据库
  • ppt素材免费网站网站正能量晚上免费软件
  • 个人淘宝客网站如何备案搭建一个平台要多少钱
  • nginx 网站建设淘客网站怎么做首页
  • 网站制作的基本步骤是手机网站建设 新闻
  • 水墨 网站源码工装
  • 任丘网站建设服务网站 建设原则
  • 长沙做一个网站要多少钱网站底部备案代码
  • wordpress构建自定义设置页面seo培训学什么
  • 延安有哪些做网站的公司如何建设网站?
  • 网站建设者属于广告经营者吗网站管理程序
  • 网站内容优化方法深圳市宝安区怎么样
  • 视频网站开发视频公司网站制作多少钱
  • 单页简洁手机网站模板购物软件
  • 素材网站官网低价网站建设费用预算
  • 苏州网站设计kgwl个人网站有什么外国广告做
  • 浙江省网站建设报价简单网站开发工具
  • 物流网站的建设wordpress电视直播插件下载
  • 简述网站开发流程青岛做网站建设价格低
  • 网站开发的业务需求分析杭州推广公司
  • 网站建设技术实现难点app开发需要哪些软件
  • 响水建设局网站做网站需要会哪些知识
  • 企业制作企业网站个人网站可以做百度竞价
  • 做网站找投资人wordpress 5 主题教程
  • 做国外网站汇款用途是什么wordpress图片主题晨曦
  • 网站设计跟网站开发区别为什么网站需要维护
  • m 的手机网站怎么做网络推广方式和方法