计算机网络学习笔记
# 五层协议体系结构

# 应用层
应用层的任务是通过应用进程间的交互来完成特定网络应用,它定义了信息交换的格式,比如 HTTP 协议中定义了报文头报文体,协议双方按这种规则来发送接受数据
这一层传输的东西叫应用层报文
如果是七层体系结构,应用层还有表示层和会话层。OSI 的七层体系结构概念清楚,理论也很完整,但是它比较复杂而且不实用,而且有些功能在多个层中重复出现。但是如此细致的分层可以让我们对网络的理解更进一步
比如在 OSI 分层的时候,会话层在第五层,用来给不同的应用建立与管理会话。表示层在第六层,用于数据的解密、加密、翻译以及压缩等操作。第七层就是应用层,每个安装在电脑上的软件在这一层发挥作用
拿 dubbo 服务调用来举例,dubbo 的 RPC 协议在第五层,对上层封装了服务调用的过程,作用就是将数据发送到另外一台机器上。dubbo 的各种序列化协议在第六层,用于处理数据的转换,转换后的数据提供给第七层使用
常用的协议有以下几种:
- DNS:管理域名与 IP 地址映射关系的分布式数据库
- HTTP 协议:超文本传输信息,为了提供一种发送接受 HTML 文件的方法
- FTP 协议:文件传输协议,20端口用于传输数据,21端口用于传输控制信息
常见端口有以下几种:
- HTTP:80、8080(阿帕奇)
- HTTPS:443
- MySQL:3306
- FTP:20,21
- DNS:53
- Redis:6379
- Nacos:8848
# 传输层
传输层的主要任务就是负责向两台终端设备进程之间的通信提供通用的数据传输服务,对上层数据进行封装切割等操作,以及保证数据传输的准确性
传输层的协议 TCP 或 UDP 加上端口就可以标识一个应用层协议,TCP/IP 协议中的端口范围是从 0~65535
传输层主要传输 TCP 报文和用户数据报,这一层的主要协议如下:
- TCP 协议:传输控制协议
- UDP 协议:用户数据协议
- ARQ 协议:自动重传请求
# 网络层
网络层的任务是选择合适的路由,为分组交换网上的不同主机提供通信服务
在计算机网络中进行通信的两个计算机之间可能会经过很多个数据链路,也可能还要经过很多通信子网。网络层的任务就是选择合适的网间路由和交换结点,确保数据及时传送
这一层传输的东西叫 IP 数据报,因为这一层使用的协议叫 IP 协议,因此互联网的网络层也叫做网际层或 IP 层
- IP 网际协议 :网际协议 IP 是 TCP/IP 协议中最重要的协议之一,也是网络层最重要的协议之一,IP 协议的作用包括寻址规约、定义数据包的格式等等,是网络层信息传输的主力协议
- ARP 协议 :ARP 协议,全称地址解析协议(Address Resolution Protocol),它解决的是网络层地址和链路层地址之间的转换问题,因为一个 IP 数据报在物理上传输的过程中,总是需要知道下一跳(物理上的下一个目的地)该去往何处,但 IP 地址属于逻辑地址,而 MAC 地址才是物理地址。具体来说是 IP 地址转 MAC 地址的一些问题
- NAT 网络地址转换协议:NAT 协议(Network Address Translation)的应用场景如同它的名称——网络地址转换,应用于内部网到外部网的地址转换过程中。具体地说,在一个小的子网(局域网,LAN)内,各主机使用的是同一个 LAN 下的 IP 地址,但在该 LAN 以外,在广域网(WAN)中,需要一个统一的 IP 地址来标识该 LAN 在整个 Internet 上的位置
为啥要有 NAT 呢,因为 IPv4 地址已经基本用完了,而 NAT 协议可以将一个局域网内的多个主机使用同一个公网 IP 地址来访问互联网,从而节省了公网 IP 地址的浪费。我们在自己的机器上看见的 IP 地址都是局域网地址,对外暴露的地址是从运营商获得的公有 IP 地址,登录路由器管理界面可以查看
关于这个问题 IPv6 是根本的解决方案。IPv6地址长度为128位,总量是2^128个,这是一个天文数字(约340万亿亿亿亿个),可以为地球上的每一粒沙子都分配一个IP地址。IPv6正在全球范围内稳步部署
# 数据链路层
数据链路层的作用是将网络层交下来的 IP 数据报组装成帧,在两个相邻节点间的链路上传送帧。每一帧包括数据和必要的控制信息(如同步信息,地址信息,差错控制等)
这一层传输的东西叫帧,一般使用的是以太网协议,也可能使用IEEE802.3、IEEE802.4等协议
帧头包含同步信息,地址信息,差错检测等信息,这样可以知道帧从哪开始、进行差错处理、了解目的地址与源地址。这里的差错校验与网络层校验的不同是,这里哦只保证一段链路的数据传输的准确性,而网络层保证的则是两个节点的准确性
Wi-Fi 就是物理层和数据链路层的东西。Wi-Fi 取代的是以太网的网线和交换机上的口,通过无线电波来收发信息
# 物理层
物理层的作用是实现相邻计算机节点之间比特流的透明传送,尽可能屏蔽掉具体传输介质和物理设备的差异,从网络层的第一层开始,已经对上层屏蔽下层的东西了,这里做的是现实事件到网络世界的转换。如果是4层网络模型,会将数据链路层与物理层糅合在一起

这一层传输的东西叫比特,就是代表0或者1的电流
# TCP 协议
三大特点:面向链接,可靠,字节流,某一特点都能说个一章节。TCP 为每个数据流初始化并且维护状态信息,比如窗口大小、端口等等,这些数据主要为了实现可靠性和流量控制
# 三次握手
1,客户端向服务器发送连接请求,即发送 SYN 报文,此时请求里包含 seq = x
开始,SYN 报文不能携带数据,需要消耗一个序列号。发送方进入 synsend(同步发送)状态
2,服务器接到请求后,向客户端返回响应,也是 SYN 报文,此时:
ACK = 1(表示是否允许链接),ack = x + 1(根据接受的请求动态算出来的,发送回去用于对客户端确定序列号),seq = y
SYN 报文不能携带数据,需要消耗一个序列号,接收端进入 synrcvd(同步接受)状态
3,客户端向服务器回复响应,此时:
ack = y + 1,ACK = 1,seq = x + 1
可以携带报文,不携带报文不消耗序列号,接收端进入连接状态
- 为什么要回传 syn?
TCP 中只要是数据,即使是一个字节,即使如 SYN,也需要确认的,毕竟可以发送报文只代表了客户端可以正常发送,并没有证明发送端可以正常接受报文
- 为什么最后还要发送一次确认/为什么不是两次握手/为什么服务器接受到连接请求后不直接建立连接
为了避免连接资源被浪费
1,为了确定服务器接受正常、发送正常,客户端发送正常、接受正常
2,以上的回答有点模糊,更准确的说法是如果服务器发送的 syn 报文丢失,接收端没有建立连接但是服务器建立了连接,连接资源会被浪费
3,如果发送端的第一次请求因为延迟收到了,但是此时接收端已经接受了第二次请求并且已经传输完数据然后关闭连接了,此时此前滞留的那一次请求连接,网络通畅了到达了服务器,此时连接资源会被浪费
# SYN 攻击是什么,如何解决
在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 则回复确认包,并等待 Client 确认,由于源地址不存在,因此 Server 需要不断重发直至超时,这些伪造的 SYN 包将长时间占用未连接队列,导致正常的 SYN 请求因为队列满而被丢弃
怎么解决呢:
1,缩短半连接等待时间 2,短时间受到某 IP 的重复 SYN 则丢弃后续请求 3,通过半连接队列的后备设置来解决
# 半连接队列
半连接状态,指 TCP 状态为 SYN_RCVD 的状态。服务器处于 Listen 状态时收到客户端 SYN 报文时放入半连接队列中
当 SYN queue 满了,系统会根据内核参数 net.ipv4.tcp_syncookies 的值来处理请求,tcp_syncookies 是用来防止 SYN flood 攻击
原理是在半连接队列满时,服务端并不丢弃 SYN 请求,而是将源目的 IP、源目的端口号、接收到的 client 端初始序列号以及其他一些安全数值等信息进行 hash 运算,并加密后得到一个 SYN cookie
server 端发送初始序列号为 cookie 的 SYN+ACK 包,并不会保存这个 cookie。如果接收到客户端的 ACK 包,server 端将 client 端的 ACK 序列号减 1 得到的值,与上述要素 hash 运算得到的值比较(这些值也是客户端的 ack 包带过来的),如果相等,直接完成三次握手,构建新的连接
# 全连接队列
全连接状态,指 TCP 状态为的状态 ESTABLISHED。TCP 的连接状态从服务器(SYN+ACK)响应客户端后,到客户端的 ACK 报文到达服务器之前,则一直保留在半连接状态中;当服务器接收到客户端的 ACK 报文后,该条目将从半连接队列移到全连接队列尾部,即 accept queue
当 Accept queue 满了以后,即使 client 端继续向 server 端发送 ACK 的包,也会不被响应,此时 ListenOverflows+1,系统会根据 net.ipv4.tcp_abort_on_overflow 参数的值来决定如何返回。值为 0 表示直接丢弃该 ACK,但不会将连接信息从 SYN queue 队列中移除。当系统丢弃了最后阶段的 ACK,系统会根据参数 net.ipv4.tcp_synack_retries 的设置重新向 client 端发送 SYN+ACK 包。而 client 端在收到多个 SYN+ACK 包,会认为之前的 ACK 丢包了,于是 client 端又重新向 server 端发送 ACK 包。当在 accept queue 有空闲的时候最终完成连接。如果 accept queue 始终满员,则最终 client 将收到 RST 包
# 四次挥手
1,某一方主动关闭方请求断开连接,即发送 FIN(seq = x) 标志的数据包
2,被动关闭方回发响应,此时数据包里包含
ack = x + 1,ACK = 1,seq = y
3,等待一段时间后,被动关闭方发送 FIN 报文
seq = z,ack = x + 1,ACK = 1
4,主动关闭方接受报文给出回应,并等待一段时间(俩倍的报文来回时间),断开连接
ack = y + 1,ACK = 1,seq = x + 1
5,被动关闭方接受报文,立即关闭连接
- 为什么需要等待两个最长报文段寿命 2MSL 的时间
主要是为了接受被动方的全部数据
1,主动关闭方的最后一个报文丢失时,被动关闭方会再次发送 fin 报文
2,客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文
3,如果B没有收到自己的ACK,会超时重传FiN,那么A再次接到重传的FIN,会再次发送ACK
- 为什么需要四次挥手,不是三次
和上面问题的理由类似,被动关闭方可能有一些数据没有发完,所以 FIN 报文与 ACK 报文需要分开
- 服务端主动关闭连接会发生什么
如果服务端主动关闭连接,那么服务端就会先发送 fin,最后要有个 2MSL 的 TIME-WAIT。如果服务端在一段时间内主动关闭的连接比较多,则服务端会有大量的 TIME-WAIT 状态的连接要等 2MSL 时间。这导致大量端口不可用
解决思路很简单,就是让服务器能够快速回收和重用那些 TIME_WAIT 的端口资源。linux 下直接对 /etc/sysctl.conf 文件进行修改即可
同时除了四次挥手外,还可以使用 RST(reset)标识强制中断连接,发送发可以发送 RST 标识的报文,通知对方立即断开连接
# 发送数据全流程
1,数据拆分:应用层产生的数据报文通常会比 TCP 报文段允许的大小大得多。TCP 会把这些数据拆分成多个更小的片段,每个片段称为一个 TCP 报文段。每个 TCP 报文段都包含一部分应用层数据以及必要的 TCP 头信息,头部中包含源端口、目的端口、序列号、确认号、数据偏移、标志位、窗口大小、校验和、紧急指针以及选项等信息
2,传输:TCP 报文段被传输层封装并传递给网络层。网络层在每个 TCP 报文段外面再添加 IP 头部,形成 IP 数据报。IP 数据报通过网络传输,经过路由器和交换机到达目的地。在传输过程中,可能会发生分片(当数据报超过 MTU 时),这些分片会在目的地重新组装。就算 TCP 分了一次片,IP 也可能会再分一次,但是出来 TCP 以外的传输层协议(UDP)可能直接依靠 IP 的分片机制
3,接收与重组:目的主机的网络层接收到 IP 数据报,将其传递给传输层。TCP 使用头部中的序列号信息将报文段重新组装成原始的数据流。即使报文段顺序到达有问题,TCP 也能根据序列号正确地重组数据
4,错误校验与确认:TCP 头部包含校验和,用于检测数据传输中的错误。接收方会对数据进行校验,确认无误后发送 ACK(确认)报文。如果报文段丢失或损坏,接收方不会发送 ACK,发送方会重传这些报文段
在整个过程中,TCP 使用流量控制(滑动窗口机制)和拥塞控制(如慢启动、拥塞避免、快速重传和快速恢复)来管理数据传输的速率,确保网络不会过载。因为滑动窗口,这期间可能发生粘包和半包问题
# 拥塞控制
1,慢启动 (Slow Start),初始状态为拥塞窗口 cwnd = 1 MSS (最大报文段大小),增长方式为每收到一个 ACK,cwnd 增加一倍,到达慢启动阈值或者丢包的时候停止增长
2,拥塞控制:当cwnd ≥ ssthresh时进入,每个 RTT 时间 cwnd 增加1 MSS(线性增长)
3,快速重传 (Fast Retransmit),发送端收到3个重复 ACK 时(表明有包丢失但后续包已到达),立即重传丢失的报文段。而超时重传则是在倒计时器到期时,还没有收到 ACK 的话,才会重试
4,快速恢复 (Fast Recovery),执行快速重传后进入,cwnd 变为当前值的一般
# KeepAlive
TCP 设有一个保活计时器,服务器每收到一次客户端的请求后,都会重新复位这个计时器,时长通常设置为2个小时,如果两个小时还没有收到客户端的任何数据,服务器就会发送探测报文段,每间隔75秒发送一次,如果一连发了10个探测报文仍然没响应,服务器就认为客户端出了问题,关闭连接
这种东西叫心跳校验机制,KeepAlive 并不是默认开启的,在 Linux 系统上没有一个全局的选项去开启 TCP 的 KeepAlive。需要开启 KeepAlive 的应用必须在 TCP 的 socket 中单独开启,默认接受时间为7200秒
有很多判断对方是否挂掉的方式都是基于 tcp 的这个校验机制,比如 http 的保活计时器、zk 集群中的心跳校验、redis 的哨兵模式等
# TCP 通信粘包和半包问题
# 是什么
TCP 粘包是指发送方多次发送的小数据包被接收方一次性接收,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾,导致 TCP 不知道这个包从哪里结束了
半包则指发送方的一个大数据包被接收方拆分成多次接收,为什么会出现这种情况呢?TCP 是面向流的协议,本身没有消息边界的概念,数据像水流一样连续传输
这个问题出现的核心原因是 TCP 是字节流服务,TCP 不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
UDP 不可能出现粘包问题,因为它不用窗口接受,每次只接受一个数据包,每个 UDP 段都是一条消息,应用程序必须以消息为单位提取数据,而 TCP 是套接字传输方式
# 原因
TCP 粘包发生的表面原因有两点:
1,发送方如果有连续几次发送的数据都很少,通常 TCP 会根据优化算法把这些数据合成一包后一次发送出去
2,接收方如果未及时清理缓冲区的数据,造成多个包同时接收
根本原因还是因为 netty 是面向字节流的传输协议,只负责消息的传输,对消息的边界不关心
# 处理
解决粘包问题有很多方案,以下提出部分方案:
1,定长发送,发送端在发送数据时都以 LEN 为长度进行分包。这样接收方都以固定的 LEN 进行接收,如此一来发送和接收就能一一对应了。分包的时候不一定能完整的恰好分成多个完整的 LEN 的包,最后一个包一般都会小于 LEN,这时候最后一个包可以在不足的部分填充空白字节
2,头尾部标记,在每个要发送的数据包的尾部设置一个特殊的字节序列,此序列带有特殊含义,跟字符串的结束符标识”\0”一样的含义,用来标示这个数据包的末尾,接收方可对接收的数据进行分析,通过尾部序列确认数据包的边界
头部标记则是定义一个用户报头,在报头中注明每次发送的数据包大小。接收方每次接收时先以报头的 size 进行数据读取,这必然只能读到一个报头的数据,从报头中得到该数据包的数据大小,然后再按照此大小进行再次读取
3,加入消息长度信息,在收到头标志时,里面还可以带上消息长度,以此表明在这之后多少 byte 都是属于这个消息的。如果在这之后正好有符合长度的 byte,则取走,作为一个完整消息给应用层使用
你可能注意到 TCP 采用头部标记与消息长度混合使用的方式防止粘包,同时还有参数校验等辅助方法保证传输的可靠性
http 已经为我们处理了这些细节,但是如果我们直接和传输层交互,比如使用 netty 则需要额外考虑这些问题
# IP层会出现粘包吗
不会
IP 层的传输类似与 UDP 传输,每次都会接受一个被切片后的数据段,就算一个 UDP 报文被切成多个字段,经过不同的路由器传输到终点,目标机器还是一个一个接受的
# TCP 如何确保数据有效传输
1,ARQ 协议:发完一个分组就停止发送,等待对方确认,如果不能及时收到一个确认,将重发这个报文段 2,拥塞控制:当网络拥塞时,减少数据的发送 3,流量控制: TCP 连接的每一方都有固定大小的缓冲空间,流量控制是对接收方的窗口进行大小控制 4,序列号:TCP 给发送的每一个包进行编号,并根据 ack 判断这个报文序号是否正确 5,效验和: TCP 将保持它首部和数据的检验和,如果效验和有错会直接丢掉
- 流量控制
通过滑动窗口来实现,接收端的滑动窗口可发送控制信息调整发送端窗口大小
- 拥塞控制
控制发送端拥塞窗口的大小,使网络中数据量合理,主要由四种算法来实现
1,慢开始:由小到大逐渐增大发送窗口,每次来回大小乘二,以指数增长 2,拥塞控制:缓慢增加,每次来回大小加一 3,快重传:快速恢复丢失的数据包。如果接收机接收到一个不按顺序的数据段,它会立即给发送机发送一个重复确认。如果发送机接收到三个重复确认,它会假定确认件指出的数据段丢失了,并立即重传 4,快恢复:如果出现数据包丢失,设置慢启动阈值(ssthresh) 为当前拥塞窗口的一半
注:发送窗口通常维持在流量控制与要塞控制窗口中较小的那一个
# UDP
无连接,不可靠,基于数据报文段,一般用于实时传输数据,微信的语音聊天功能之前是使用 UDP 做的
这里重点说一下数据报文段,基于数据报是指无论应用层交给 UDP 多长的报文,UDP 都照样发送,即一次发送一个报文。至于如果数据包太长,需要分片,那也是 IP 层的事情,大不了效率低一些。UDP 对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界
# ARQ 协议(自动重传请求)
用于传输层与数据链路层,它通过使用确认和超时这两个机制,在不可靠服务的基础上实现可靠的信息传输
# 停止等待 ARQ
传出一个数据,等待,接受回应后,继续传输 如果没有回应,重新发送 接收端收到相同数据,丢弃数据,发送回应 发送端收到相同回应,丢弃数据
# 连续 ARQ
发送端:维持一个滑动窗口,发送窗口里的所有数据 接收端:发送最后一个收到连续数据的回应