计算机网络学习笔记
TCP
TCP连接建立三次握手过程
首先客户端会向服务端发送 SYN 报文建立连接,如果迟迟收不到服务端返回的报文,即第二次握手失败了,就会触发超时重传的机制,重新发送 SYN 报文,重新发送的报文的序列号是一样的,重传是有次数上限的,如果最终还是没有接收到返回的报文,客户端则会断开连接
然后当服务端成功接收到来自客户端的 SYN 报文后,会返回确认客户端的 SYN 报文的 ACK 报文,以及向客户端发起建立连接的 SYN 报文,如果服务端没有接收到客户端返回的 ACK 报文,即第三次握手失败了,则同样会触发超时重传机制,重传 SYN + ACK 报文,也同样有次数上限,若最终还是未接收到客户端的 ACK 报文,服务端就会断开连接;
最后当客户端接收到服务端的报文后,再返回确认服务端的 SYN 的 ACK 报文,在第三次握手中客户端的 ACK 报文不会重传,当服务端没有接收到这个ACK 报文会重新发送 SYN + ACK 报文,客户端接收到后就发送对应的 ACK 报文,如果最终服务端还是没有接收到就会断开连接;
之所以是三次握手,简单来说,
- 是为了确保客户端和服务端双方都能发送和接收
具体来说,
- 一来是可以防止历史旧的重复连接初始化造成混乱,假设过往已经发起了一个连接,SYN 的序号为 90,然后现在发起了一个新的连接,新的 SYN 的序号为 100,服务端先收到了旧的报文,将 ACK 的序号设置为 90 + 1 即91进行了返回,之后客户端收到这个 ACK 报文,发现序号和预期的 101 不一致,这时客户端就发送 RST 报文告诉服务端中止这个旧的连接,后续当服务端接收到新的 SYN 报文就可以进行正常的三次握手建立连接,这里中止的过程就进行了三次握手,如果只是一次或两次,都无法实现
- 二来是可以确保双方的序列号是同步的,当客户端发送了 SYN 报文,就需要服务端返回 ACK 确认标识客户端的 SYN 报文已被成功接收,然后服务端发送的 SYN 报文同样需要客户端返回 ACK 确认成功被接收,这样一来一回才能确保双方的初始序列号等到可靠的同步,如果只有两次握手,就只能确认一方的初始序列号被对方成功接收,无法保证双方的序列号都能被成功接收
- 三来是避免资源的浪费,如果采用两次握手,当客户端发送一次 SYN 报文后,如果此时因为网络阻塞迟迟未接收到 ACK 响应,客户端就会重新发送新的 SYN 报文,接着当服务端接收到了第一次的 SYN 报文后,正常返回 ACK 响应,因为服务端不清楚客户端是否收到了自己发送 ACK 报文,所以只能先建立一个连接,然后服务端陆陆续续又接收到了来自客户端的 SYN 报文,同样也会建立连接,这样在服务端就创建了好几个同一个客户端的重复冗余无效的连接,造成了不必要的资源浪费
TCP关闭连接四次握手过程
首先客户端会先发送一个 FIN 报文告诉服务端打算关闭连接了,发送后客户端进入 FIN_WAIT_1 状态,
- 如果没有接收到服务端返回的 ACK 确认报文,则会触发超时重传的机制,重新发送 FIN 报文,当达到了次数上限后,就不再重传,等待一段时间后直接进入
CLOSE状态关闭连接;
然后服务端成功接收到客户端的 FIN 报文后,就先向客户端发送 ACK 报文,再进入 CLOSE_WAIT 状态,客户端接收到后进入 FIN_WAIT_2状态,
- 服务端的 ACK 报文不会触发超时重传的机制,由客户端重新发送 FIN 报文,然后服务端再发送 ACK 报文返回;
接着,如果服务端还有数据没有处理和发送完成,则会先等待完成,然后服务端才向客户端发送 FIN 报文告诉客户端同意关闭连接;
- 如果客户端没有接收到 FIN 报文,则会继续等待,等待超时后就直接关闭连接;
如果服务端迟迟没有接收到客户端的 ACK 报文,则会触发超时重传机制,超过次数上限后也直接关闭连接;
- 这里如果服务端没有数据要发送了且开启了TCP延迟确认机制,那么服务端就会将前面的 ACK 报文和 FIN 报文合并传输,就少了一次握手,变成了三次握手;
回到四次握手过程,最后,当客户端接收到了服务端的 FIN 报文后,先返回 ACK 报文,再进入 TIME_WAIT 状态,过一段时间后再进入CLOSE 状态,服务器接收到 ACK 报文后,进入 CLOSE 状态
让TCP可靠的机制
重传
超时重传
当超过一定时间没有接收到应答时触发
RTO (Retransmission Timeout 超时重传时间)判定略大于RTT,RTO是动态的
快重传
当连续收到3个相同应答序列号时触发
SAK(Selective Acknowledgment, 选择性确认)
接收方在TCP的头部上添加一个SAK字段,纪录已接收到的数据,让发送方知道哪些数据是已经被接收了,然后在连续收到3个ACK应答时,就只重传未接收的数据
D-SAK(Duplicate SACK)
主要使用了 SACK 来告诉发送方有哪些数据被重复接收了
滑动窗口
主要解决一来一回的通信中RTT越长,通信效率越低的问题
设定一个指定大小的窗口,该窗口内的数据进行连续发送而无需等待应答,当可用的窗口大小为0时,在等待得到应答之前都不会进行数据发送
窗口大小由接收方通过TCP头部的window字段决定
流量控制
主要解决发送方无限制的向接收方发送数据,导致出现过多的重传带来的网络流量浪费问题
这个机制就是让发送方可以根据接收方的实际接收能力来进行控制数据发送量
拥塞控制
窗口大小:
cwnd慢启动门限:
ssthresh
慢启动
当
cwnd<ssthresh发送方每接收到一个应答,窗口大小
cwnd就加1,呈现一个指数增长
拥塞避免
当
cwnd>=ssthresh发送方每接收到一个应答,窗口大小
cwnd就加1 / cwnd,呈现一个线性增长
拥塞发生
采用超时重传
ssthresh设置为cwnd / 2cwnd重置为 1- 重新进入慢启动阶段
采用快重传
cwnd设置为cwnd / 2ssthresh设置为cwnd即,ssthresh设置为拥塞发生时的窗口大小的一半- 进入快速恢复
快速恢复
cwnd设置为ssthresh + 3,即设置为拥塞发生时的窗口大小的一半 加 3重传丢失的数据
收到重复的ACK,
cwnd + 1当收到新数据的ACK,
cwnd设置为ssthresh,即设置为拥塞发生时的窗口大小的一半- 进入拥塞避免
HTTPS
TLS 四次握手过程
首先,客户端会先向服务端发送 Client Hello消息,消息内容包含了 TLS 版本、一串随机数 Client Random 以及支持的密码套件列表,
然后,服务端会陆续向客户端发送
Server Hello消息,同样包含 TLS 版本、一串随机数Server Random以及从列表中选择的密码套件;- 还有服务端的数字证书;
- 最后是
Server Hello Done消息,告诉客户端已经把该发的都发了
接着,客户端再接收到服务端的数字证书后,会使用 CA 的公钥验证证书并从中取出服务端的公钥
接下来,客户端会再生成一个随机数pre-master,并用服务端的公钥进行加密,客户端接着
- 再向服务端发送
Client Key Exchange消息,把新生成的随机数发给服务端,服务端接收后,这样双方都有了3串相同的随机数,那么就可以基于这三串随机数生成一个会话密钥,后续就可以采用对称加密的方式进行数据的发送; - 客户端生成会话密钥后,会再发送一个
Change Cipher Spec消息告诉服务端开始使用这个会话密钥加密的方式发送消息,然后再发一个Encrypted Handshake Message消息,消息里面是将之前发送过的数据做个数据摘要后再使用密钥加密后的数据,让服务端验证加密通信是否可以,并且判断之前的握手信息是否又被纂改
最后,服务端同样也发送前面客户端发送的两个消息,验证客户端的加密,并判断之前的信息是否又被纂改,如果都验证没问题了,那么握手就正式完成,后续就采用加密传输
Websocket
建立过程
首先,客户端在于服务端成功进行 http 3次握手后,会向服务端发送一个携带有升级协议信息的特殊 header 和一串随机生成的 base64 码的请求
接下来,服务端在接收到这个请求后,如果服务端支持 Websocket 协议升级,就会获取里面的 base64 码,然后使用一个公开的算法将其转为一个字符串,并将这个字符串放入到响应中进行返回,响应的状态码是 101,指的就是协议切换
最后,客户端接收到响应后,同样会使用和服务端同一公开算法将生成的 base64 码转为一个字符串,与服务端返回的字符串进行对比,如果字符串一致,那么就成功验证,Websocket 连接就建立完成,后续就使用 Websocket 的数据个数进行通信
Websocket 建立过程只需要进行 2次 握手
SSH
连接建立过程
首先 ssh 客户端会先与 ssh 服务端进行 TCP 3 次握手,
然后,客户端使用 ssh 协议向服务端发送所使用的 ssh 版本,服务端接收后先返回 ACK 确认,再发送服务端使用的 ssh 版本,这个阶段做的是版本协商,
接下来,客户端和服务端互相发送 Key Exchange Init 消息,里面包含了支持的加密算法列表,这个阶段做的是算法协商,
接着,客户端和服务端会互相使用 DH(Diffie-Hellman)密钥交换算法生成共享会话密钥,后续的用户认证和交互通信就会采用这个会话密钥进行加密,
在进行 ssh 身份验证中,
- 服务端会先向客户端发送自己的主机公钥,然后客户端会在自己本地的 known_hosts 中检查是否有记录,如果没有则需要用户进行确认添加,如果有则会进行比对指纹,若指纹一致则通过,否则拒绝访问,因为这里可能是中间人攻击
- 客户端验证了服务端的身份后,客户端会向服务端声明使用哪个公钥,然后服务端向客户端发送一个挑战码,让客户端使用其私钥进行加密后发送,服务端接收后使用公钥进行解密验证,若通过则允许访问,若不通过但服务端启用了密码登录,则进行密码验证
DNS
基本描述
DNS(Domain Name System)是用于根据域名地址查询实际的IP地址的系统
基于UDP协议,之所以采用此协议,是因为UDP:
- 低延迟:UDP无连接,不需要在数据传输前建立连接,从而减少传输时延,适合DNS这种需要快速响应的场景
- 简单快速:UDP相比于TCP更简单,没有涉及复杂的连接管理和流量控制,传输效率更高,适合DNS这种需要快速传输的场景
- 轻量级:UDP头部较小,占用的网络资源小,适合DNS这种需要频繁且短小的数据交换场景
工作流程
假设现在访问www.webserv.top域名
- 先从本地DNS查询域名地址,如果本地有缓存IP地址,那么就直接返回
- 如果本地没有对应的IP地址缓存,那么本地DNS会向根域名(
.)服务器查询www.webserv.top的地址,然后根域名服务器返回.top顶级域名的服务器地址 - 接着本地DNS再访问
.top顶级域名服务器,查询www.webserv.top的地址,顶级域名服务器返回webserv.top的区域权威DNS服务器地址 - 最后本地DNS访问区域权威DNS服务器,查询得到
www.webserv.top的IP地址,并缓存该地址