Posix API 与网络协议栈

Posix API 与网络协议栈

TCP协议的特点

  • 面向连接
  • 点对点:连接的只有两个端点
  • 可靠传输:无差错、不丢失、不重复,有序
  • 全双工通信:双向通信,两端设有发送缓存和接收缓存
  • 面向字节流

tcp 相关的 Posix API

  • 服务端: socket - bind - listen - accept - recv - send - close
  • 客户端: socket - connect - send - recv - close

查看网络相关状态命令:netstat -nat

1、TCP 报文段

在这里插入图片描述

tcp 报文段分为 tcp 首部和 tcp 数据两部分

  • 源端口和目的端口
  • 序号 seq:本报文段发送的数据的第一个字节的序号。tcp面向字节流,为数据流中的每一个字节编上一个序号
  • 确认号 ack:期望收到对方的下一个报文段的数据的第一个字节的序号。确认号为n,表示序号 n-1 前的所有数据都已经正确收到。序号 + 有效荷载 = 确认号
  • 数据偏移:首部长度,单位4B
  • 窗口:允许对方发送的数据量,单位1B
  • 校验和:首部 + 数据

2、连接管理

每个 tcp 连接都有三个阶段:连接建立、数据传输和连接释放。

2.1、连接建立

2.1.1、posix API
  • socket():为文件系统分配fd,配置tcb(tcp control block)
  • bind():为tcp绑定本地的ip和端口。客户端bind(opt),不绑定则随机端口。
  • listen(): 把tcb 状态置位 listen,参数backlog指的是全连接队列的长度。
  • connet(): 向服务端发送SYN报文,开始协议栈的三次握手,并等待三次握手的返回结果。
  • accept():阻塞,直至全连接队列非空。此时,从全连接队列中取一个tcb结点并为其分配1个 socket。
2.1.2、三次握手
tcp 建立三次握手的时候,内核做了哪些事情

服务端接收第一次握手,回复第二次握手的同时,把未建立完的 tcp 连接放入半连接队列里面。

第三次握手成功后,服务端收到 ACK 报文,从半连接队列中取出 tcb 结点放入全连接队列,发出通知,accept()收到信号后从全连接队列后取出1个 tcb 结点,并为其分配1个 socket 。

因此三次握手发生在客户端调用connect()后,服务端调用listen()accept()执行前。

在这里插入图片描述

2.1.3、常见面试题
问题1、三次握手的原因

防止旧的重复连接引起连接混乱的问题(为什么不是两次握手)

  • 服务端收到已断开连接的 SYN 报文,服务器陷入忙等状态,

  • 客户端未收到 ACK 报文,而服务器端已处于ESTABLISHED状态,等待客户端发送数据,造成资源浪费

为什么不是四次握手?没必要,浪费资源。

问题2、SYN flood 攻击原理
  • 半连接队列:当客户端发送的SYN被服务端接收后,该连接加入syn队列,状态SYN_RCVD
  • 全连接队列:当客户端返回的ACK被服务端接收后,该连接加入accept队列

SYN flood 攻击原理:伪造大量的 ip 并向服务端发送SYN,服务器端大量连接处于SYN_RCVD,充满半连接队列。造成的后果是:

  • 无法处理正常的请求
  • 服务器不断重发ACK报文,直到资源耗尽。

解决:

  • 减少SYN + ACK 重试次数,避免大量的超时重发;
  • 利用 SYN Cookie 技术,在服务端接收到SYN后不立即分配连接资源,而是根据这个SYN计算出一个Cookie,连同第二次握手回复给客户端,在客户端回复ACK的时候带上这个Cookie值,服务端验证 Cookie 合法之后才分配连接资源。
问题3、双方同时发送 SYN 报文
  • 发完 SYN,两者的状态都变为SYN-SENT
  • 在各自收到对方的SYN后,两者状态都变为SYN-REVD
  • 回复对应的ACK + SYN,这个报文在对方接收之后,两者状态一起变为ESTABLISHED

在这里插入图片描述

问题4、已经建立连接的一方突然断开

TCP 的 KeepAlive保持服务端与客户端的连接,一方不定期发送心跳探活包,另一方回复 ACK。若另一方断开连接,无法响应,返回RST,则释放当前链接。

区分 http 的 keep-alive:短时间内连接可以复用。

2.2、数据传输

2.2.1、posix API
  • recv()fd对应的内核态tcbreadbuffer的数据拷贝到用户态
  • send() 将用户态的数据拷贝到fd对应的内核态tcbsendbuffer

在这里插入图片描述

2.2.2、tcp 分包与粘包问题

原因:tcp面向字节流,没有边界,不能通过send返回值判断。解决这一问题有两种方案:

  • 应用层协议首部增加数据报的长度 pktlen

    read(tcphdr, 2);
    read(tcphdr->length);
    while (cout < tcphdr->length) {
        size = read(tcphdr->length - count);
        count += size;
    }
    
  • 为每个包加上分隔符,如'/r/n'

    read(buffer, 1024);
    buffer[idx] = '/r/n';
    pktlen = &[idx + 2];
    

2.3、连接释放

2.3.1、posix API

close(): 将FIN报文放入sendbuffer中,回收fd

2.3.2、四次握手

在这里插入图片描述

2.3.3、常见面试题
问题1、服务端出现大量close_wait状态

原因:客户端主动关闭连接(recv返回0),服务端忙于读写,未及时关闭连接close()

解决:业务数据处理与网络层处理异步分离

问题2、双方同时关闭连接

双方变化状态是一致的,即双方发送了FIN包后,收到了对方的FIN包,进入 CLOSING 状态。

在这里插入图片描述

  • 双方发送FIN报文后,进入FIN_WAIT_1状态
  • 接收到对方的FIN报文后,进入CLOSING状态,并向对方发送ACK
  • 接收到对方的ACK报文后,进入TIME-WAIT状态,等待2MSL后,关闭连接

在这里插入图片描述

问题3、主动断开为什么会有TIME_WAIT状态

避免由于最后一次握手时ACK包的丢失造成问题,原因如下:

  • 1、保证四次握手顺利完成
    假设第四次挥手的ACK丢失,此时主动断开方认为对端没有收到FIN报文,该连接没有正常断开,重发第三次挥手的FIN报文。

  • 2、保证旧的报文在网络中消失

    连接的 socket 五元组信息相同,可看作是同一个连接。若已经失效的报文请求出现在本次连接中,则造成数据蹿链。

问题4、TIME_WAIT状态为什么是2MSL

MSLMaximum Segment Lifetime,报文最大生存时间

确保对端没有收到第四次ACK报文时重传的FIN报文可以到达主动断开方。(ACK+超时重传FIN)

问题5:四次握手的原因

TCP是全双工的连接,需要把两个方向上的数据传输都断开

问题6:服务器端能否主动断开连接?

可以,但是主动断开连接的一方会进入TIME_WAIT状态,该状态会持续 2MSL 的,造成服务器端的资源浪费。可以设置服务器的网络地址为可重用,可解决该问题。

2.4、tcp 状态转移图

在这里插入图片描述

3、可靠传输机制

  • 序号:保证数据有序提交给应用层
  • 确认:累计确认 + 延迟 ACK
  • 重传:
    • 超时:计时器到期未收到确认则重传对应的报文
    • 冗余确认:当收到失序报文时向发送端发送冗余ACK

4、流量控制

4.1、高效发送的因素

如何让tcp高效的发送数据,主要考虑两个因素:

  • 对方还能接收多少 (流量控制)
  • 网络上还能发送多少(拥塞控制)

结论:发送窗口 swnd = min (rwnd, cwnd),这里我们先来介绍流量控制。

流量控制指的是点对点通信量的控制,匹配发送方的发送速率和接收方的读取速率,避免发送方发送速率过快,接收方来不及读出,导致接收方缓存区溢出。

方法:在确认报文中设置接收窗口rwnd的值来限制发送速率。对应 tcp 报文首部的窗口 window字段。先收缩窗口大小,再减少缓存。

传输层流量控制和数据链路层流量控制的区别

  • 数据链路层定义两个中间的相邻结点的流量控制,窗口大小固定
  • 传输层定义端到端用户间的流量控制,窗口大小动态变化

4.2、滑动窗口

tcp 采用选择重传 ARQ协议 + 累计确认

在这里插入图片描述

发送窗口有三个指针:

  • SND.WND:表示发送窗口的大小,大小是由接收方指定的
  • SND.UNA:指向已发送但未收到确认(发送窗口)第一个字节的序列号
  • SND.NXT:指向未发送但在可发送范围内(可用窗口)的第一个字节的序列号

3 个指针将滑动窗口分为 4 个部分

  • 已发送并收到ACK
  • 已发送未收到ACK,SND.UNA指向
  • 未发送但在接收方处理范围内,SND.NXT指向
  • 未发送超过接收方处理范围内,SND.UNA + SND.WND指向
 可用窗口大小 =  SND.WND -(SND.NXT - SND.UNA) = SND.UNA + SND.WND - SND.NXT

接收方滑动窗口

在这里插入图片描述

接收窗口有两个指针

  • RCV.WND:表示接收窗口的大小,它会通告给发送方。
  • RCV.NXT:指向期望从发送方发送来的下一个数据字节(接收窗口)的序列号

两个指针将滑动窗口分成三个部分

  • 已接收数据并确认,等待应用进程读取
  • 未收到数据但可以接收,RCV.NXT 指向
  • 未收到数据且不可以接收,RCV.NXT + RCV.WND指向

5、拥塞控制

概念:全局性的过程,防止过多数据注入网络,使网络能够承担现有负荷。

方法:根据自己估算的网络拥塞程度设置拥塞窗口 cwnd 来限制发送速率。

"如何判断网络拥塞?"

发送方没有在规定时间内接收到 ACK 应答报文,发生了超时重传,则认为网络出现了拥塞。

超时的计算:rtt(new) = 0.9 * rtt(old) + 0.1rtt

5.1、拥塞的控制算法

1、慢启动

tcp 刚建立好连接并开始发送数据时,cwnd = 1,执行慢启动

规则:发送方每收到1个 ACK,cwnd + 1(1个MSS: 最大报文段长度)

每经过一个 RTT,cwnd 加倍 ,cwnd指数增加。一直到达慢启动门限 ssthresh,改用拥塞避免算法。若 2cwnd > ssthresh,则下一个 RTT 时:cwnd = ssthresh

在这里插入图片描述

2、拥塞避免

当拥塞窗口 cwnd 超过慢启动门限 ssthresh 就会进入拥塞避免算法。

规则:发送方每收到1个 ACK 时,cwnd + 1/cwnd。此时,每经过1个RTT,cwnd + 1

在这里插入图片描述

随着拥塞窗口的线性增加,当网络出现丢包现象,触发了重传机制,进入拥塞发生算法。

3、拥塞发生算法

当网络出现拥塞,也就是会发生数据包重传,重传机制主要有两种,注意区分:

  • 超时重传:超时,慢启动
  • 快速重传:收到3个冗余ACK,快恢复
“为什么超时执行慢启动,而收到3个冗余ACK却执行快恢复”

收到3个冗余 ACK 时,说明网络虽然拥塞,但是至少还有 ACK 报文能够正确被交付。而当超时发生时,说明网络可能已经拥塞到连ACK报文都传输不了,发送方只能等待超时后重传数据,因此,超时发生时,网络拥塞更严重,发送方应该最大限度地一直数据发送量,cwnd重置为1。

超时重传

发生超时重传时

  • ssthresh 设为 cwnd/2
  • cwnd 重置为 1
  • 慢启动

在这里插入图片描述

快速重传和快恢复

当接收方发现丢了一个中间包的时候,发送三次该包前一个包的 ACK,于是发送端就会快速重传,不必等待超时再重传。

  • cwnd = cwnd/2
  • ssthresh = cwnd;
  • 快速恢复算法

快速恢复算法

  • cwnd = ssthresh + 3 ( 有 3 个数据包被收到了);或cwnd = ssthresh
  • 重传丢失的数据包

在这里插入图片描述

完整的 tcp 拥塞控制算法如下:

在这里插入图片描述

6、参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值