传输层网络协议
1. UDP协议
1.1 特点
- 面向数据报(DatagramSocket)
- 数据报大小限制为64k
- 全双工
- 不可靠传输
- 有接收缓冲区,无发送缓冲区
1.2 UDP协议端格式
1) 源端口
表明数据从哪里来
2) 目的端口
表明数据发往哪里
3) UDP报文长度
表明当前的报文有多长.
4) UDP校验和
表明当前数据是否正确, 在UDP中一般是使用较为简单的校验方式.
1.3 模型
以图示进行说明:
从上图中也可以看出来:
- UDP并没有所谓的建立连接的过程,只是需要发送数据的时候就去调用socket.send()进行发送。
- 一共经历了两次send()和receive()操作
1.4 代码
1) 客户端代码
package UDP;
import java.io.IOException;
import java.net.*;
public class Server {
private DatagramSocket serverSocket = null;
public Server(int port) throws SocketException {
serverSocket = new DatagramSocket(port);// 服务器端需要指定端口
}
public void start() throws IOException {
while (true) {
System.out.println("等待客户端连接...");
// 1. 构造数据报
byte[] bytes = new byte[1024];// UDP接收信息不可超出 64*1024b
DatagramPacket reqPacket = new DatagramPacket(bytes, bytes.length);
// 2. 接收数据报并解析
serverSocket.receive(reqPacket);// 输出型参数
String req = new String(bytes, 0, reqPacket.getLength());
System.out.println("收到服务器请求: " + req);
// 3. 构造响应并发送数据报
String resp = process(req);
DatagramPacket respPacket = new DatagramPacket(resp.getBytes(), 0, resp.getBytes().length, reqPacket.getSocketAddress());
serverSocket.send(respPacket);
System.out.println("已反馈: " + resp);
}
}
private String process(String req) {
return req;
}
public static void main(String[] args) throws IOException {
Server server = new Server(2024);
server.start();
}
}
2) 服务端代码
package UDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Scanner;
public class Client {
private DatagramSocket clientSocket = null;
public Client() throws SocketException {
clientSocket = new DatagramSocket();// 客户端是随机端口
}
public void start() throws IOException {
while (true) {
System.out.print("->");
// 1. 构造请求并发送
Scanner scanner = new Scanner(System.in);
String req = scanner.next();
DatagramPacket reqSocket = new DatagramPacket(req.getBytes(), 0, req.getBytes().length, new InetSocketAddress("127.0.0.1", 2024));
clientSocket.send(reqSocket);
System.out.println("已发送: " + req);
// 2. 接收服务器请求
byte[] bytes = new byte[1024];
DatagramPacket respPacket = new DatagramPacket(bytes, 0, bytes.length);
clientSocket.receive(respPacket);
String resp = new String(respPacket.getData(), 0, respPacket.getLength());
System.out.println("收到服务器反馈: " + resp);
}
}
public static void main(String[] args) throws IOException {
Client client = new Client();
client.start();
}
}
运行结果:
3) 注意事项:
-
发送的时候必须在DatagramPacket中指定目的地,即:
-
客户端:
new InetSocketAddress("127.0.0.1", 2024)
-
服务端:
reqPacket.getSocketAddress()
-
-
接收数据报的时候并不需要指定任何地址或者端口。
-
UDP接收信息不可超出 64*1024b
-
数据报中包含了一切所需要的信息,Datagram’Socket只是辅助完成发送和接收的操作。
2. TCP协议
全称为传输控制协议(Transmission Control Protocol).
2.1 特点
- 面向字节流
- 无数据报大小限制
- 全双工
- 可靠传输
- 有接收缓冲区,有发送缓冲区
2.2 TCP协议端格式
1) 源端口号\目的端口号
同UDP, 表明数据从哪来, 到哪去
2) 序号\确认序号
表明当前数据传输到哪一位
3) 首部长度
表明了当前的TCP的报头的长度, 因为TCP的报头长度有固定的20字节, 其余还有选项部分, 可将TCP的报头最大扩充到60字节
4) 保留位
为了将来扩充TCP报文使用
5) 6位标志位
-
URG: 紧急指针是否有效
-
ACK: 确认信号报文段
-
PSH: push, 提示消息接收方应当立即将数据传送到应用, 而不是留在缓冲区
-
RST: reset, 复位报文段, 表明当前连接出现严重差错, 需要进行释放后重新连接.
-
SYN: synchronization, 同步报文段, 表明发送方想要建立连接
-
FIN: finish, 结束报文段, 表明当前报文传输完毕
2.2 TCP原理
1) 确认应答
一共有两点注意事项:
- TCP对于数据报中的每一个字节都进行了标号, 确保数据接收方收到无序的数据仍能组合为有序数据.
- 当接收方收到数据时, 会发送ACK报文表明自己收到了数据, 同时这个报文中会携带着确认序号, 这个序号一共有两层含义:
- 表示自己已经收到了数据
- 表示下一个应该收到的数据报是从第几个序号开始
2) 超时重传
超时重传会发生在双方无法正常进行数据传送的时候, 主要有两种情况:
-
发送方的数据报丢包
-
接收方的ACK报文丢包
超时的时间怎么确定?
超时的时间会呈现越来越长的特性, 因为一次重传不成功, 再重传说明可能是路径的问题, 但是两次三次不成功大概率是本来就传不过去, 所以需要指数形式递增
3) 连接管理 (安全连接机制)
分为三次握手, 四次挥手
三次握手
- 客户端发送 SYN 报文, 请求建立连接
- 服务器返回 ACK+SYN 报文, 表明收到报文, 并同意建立连接请求
- 客户端返回 ACK 报文, 表明收到服务器的同意报文
四次挥手
-
客户端发送FIN报文, 进入 FIN_WAIT1 状态
-
服务器收到后, 返回ACK确认报文, 表明收到客户端想要关闭连接的请求, 同时服务器进入 CLOSRED_WAIT 状态
-
随即服务器发送FIN报文, 表明它也准备关闭连接, 同时进入 FIN_WAIT_1 状态
-
客户端收到服务器的 FIN 报文后, 发送 ACK 确认报文, 进入 TIME_WAIT_2 状态
-
服务器收到客户端的报文后, 进入 CLOSED 状态
-
一段时间后, 客户端进入 CLOSED 状态
其他状态解释
- CLOSE_WAIT 是指发送 FIN 报文达到的状态
- TIME_WAIT 是指接收到 FIN 报文达到的状态
- LISTEN 是指服务器的ServerSocket已经创建好, 并且绑定端口完成, 就等客户端发送数据, 是 “监听” 状态
- ESTABLISHED 是指客户端与服务器已经建立好连接, 是 “确立完成状态”
4) 滑动窗口 (效率机制)
一问一答(一个数据, 一个 ACK 确认报文) 的方式效率较为低下, 所以衍生出了 滑动窗口
过程
假设滑动窗口的大小就是5个数据报.
-
发送前5个数据报都不需要服务器反馈 ACK确认报文, 直接发送
-
但是此时服务器会阻塞, 直到服务器返回第一个数据报的确认报文(ACK 携带着第二个报文的序号, 但是第二个报文已经发送完毕), 第六个数据报才会进行发送
数据发送乱序怎么办?
通过序号可以对于数据进行重新排序
数据发送出现 “后发先至” / “先发后至” 怎么办?
通过确认序号, 大的序号可以覆盖确认小的数据报已经收到的消息.
过程中出现丢包怎么办?
分为两种情况:
-
一是数据报已经到达, 但是接收方的 ACK 确认报文丢包
-
二是发送方的数据报丢包
注意事项
-
值得一提的是, 一个滑动窗口需要使用缓冲区对于已经传送到的数据进行记录, 如果没有传输完成, 且已经达到应该传送完毕的状态, 那么就在下一次的确认报文中一直附带这个序号, 一直向发送方进行索要数据报.
-
滑动窗口的实际大小不仅仅是 16位窗口大小, 还需要乘以窗口扩展因子
5) 流量控制 (安全机制)
这个机制就像小学的奥数题:
这里的放水就相当于处理数据报, 蓄水相当于发送数据报, 只是我们的目标是使得 TCP 缓冲区这个 “游泳池” 不满, 所以需要进行控制 “蓄水” 的速度, 以匹配 “放水” 的速度.
因此, 根据接收端的数据处理能力, 进行控制发送端的数据发送速度的机制, 就叫做 “流量控制”.
过程
-
发送方不停发送数据报, 使得缓冲区的可用空间持续减少
-
每次缓冲区减少的时候, 都通过 ACK 确认报文携带剩余空间进行发送
-
缓冲区减到0之后, 发送方就不再发送数据, 转换为 “投石问路”, 每隔一段时间就发送窗口探测数据段,使接收端把窗⼝⼤⼩告诉发送端.
-
当缓冲区把数据消化掉之后, 就又进行传输
6) 拥塞控制 (安全机制)
这个机制是为了防止一开始不明确当前网络环境就发送大量的数据报, 会造成 “雪上加霜” 的情境.
由于滑动窗口机制的存在, 所以发送方无时不想使得窗口变得更大, 增加网络吞吐量.
因此, 产生了拥塞控制来控制每次数据传输的窗口大小. 同时引入慢启动机制进行控制
过程
- 引入"拥塞窗口" 概念
- 刚开始拥塞窗口只有1, 一次只能发送一个数据报
- 随着成功发送, 每次收到一个 ACK 确认报文, 都会使得拥塞窗口指数增加, 也就是说每次能够发送的数据条数指数增加
- 随着拥塞窗口的增大, 网络也会随之拥堵起来, 当拥塞窗口的大小达到慢启动的阈值后, 就变为线性增长
- 线性增长到一定大小, 会发生丢包现象, 此时就会重新进行规划拥塞窗口
- 旧版本 (慢启动): 拥塞窗口从0开始, 再次经历指数 - 线性 - 达到阈值停止的过程
- 新版本 (快恢复): 拥塞窗口从崩溃的1/2处重新进行线性增长, 然后重新达到阈值
7) 延迟应答 (效率机制)
为了提高 “一问一答” 式的效率, 衍生出 “多问一答”, 也即延迟应答机制
这种机制同样是通过确认序号进行操作.
- 在收到多次数据报之后
- 只返回最大序号的 ACK 确认应答报文
- 可以覆盖性应答之前小序号的数据报
8) 捎带应答 (效率机制)
这种机制体现在 “三次握手” 中最为明显, 服务器收到请求后, 既要发送 ACK 确认收到请求, 还要发送 SYN 确认连接报文, 这两个报文的发送时间又相差不了多久, 所以就发生了, SYN + ACK 报文一次同时发送两个报文的情况, 称为 “捎带应答”.
普遍的情况是发生在一问一答式中, 可以通过一次发送, 同时发送 ACK 与 应答报文. (也即尽可能将能够合并的报文合并发送)
为什么四次挥手不能合并为三次挥手?
因为第二次\第三次挥手发送的 FIN 与 ACK 报文之间的时间差距较大, 所以不能进行合并.
但是可能由于延时应答和捎带应答的存在, 可能会合并为三次挥手.
9) 面向字节流 – 粘包问题
由于 TCP 是面向字节流的传输协议, 并不是像 UDP 一样每次都发送一个完整的数据报, 所以 TCP 的数据接收方只会收到一大串的数据和他们的序号, 使得接收方不能知道每个数据的起始和结束位置, 所以产生了粘包问题.
解决粘包问题的关键 – 分隔出两个包的边界
- 约定一个正文内容中不会出现的符号作为数据之间的分隔符
- 在每个应用层数据报前加上一段空间用来表示这段数据报的长度
10) 异常情况
进程崩溃终止
这种情况, 会触发回收文件资源, 因为 TCP 连接的生命周期长于进程, 所以在进程崩溃后, TCP 仍然未断开连接, 仍然可以完成四次挥手的过程, 安全退出.
其中一方按照流程关机
关机前会由系统对于所有进程进行强行关闭, 所以此时也会进行四次挥手, 但是可能由于挥的不是特别快, 但就算再慢也能够发送一个 FIN 报文出去, 另一方会发送出 FIN 与 ACK报文, 但是在没有收到后续的 ACK 报文后, 就会触发超时重传, 但是重传之后还是没有回应, 那么另一方也会单方面释放资源.
其中一方突然断电
首先, 机器瞬间关机会导致来不及进行四次挥手, 就连发送一次 FIN 报文的时间都没有.
分为:
-
发送方断电
-
接收方断电
3. IP协议
3.1 格式
4位协议: 表明当前是IPv4还是Ipv6
4位首部长度: 单位是 “4字节”, 表明当前IP报文头部有多长, 4位能够表示的最大数字是15, 乘以4最大是60, 所以 IP 报头最大长度是60字节
8位服务类型: 只有4位有效, 而且这4位彼此之间冲突
16位总长度: IP数据报整体的长度
3位标志位: 只有两位有效, 其中一位是保留位
13位片偏移: 描述当前报文分片在整体报文中的位置, 实际偏移的字节数是这个值*8得到的, 所以除了最后一个分片, 其他的分片长度都需要是8的整数倍
8位生存时间(Time To Live): 单位是 “次数”, 是指当前报文在整个网络路径中剩余能够转发的次数
8位协议: 表示传输层使用哪个协议
16位首部校验和: 表示 IP 报文首部的校验和
32位源\目的IP地址: 表示收\发地址, 是设备在网络中的唯一标识
3.2 IP 地址管理
IP 地址本质上是32位二进制数, 由于表示起来太长, 就使用了==“点分十进制”==进行表示: 192.168.1.1
- IP 地址是网络中设备的唯一标识符, 用来确定一台主机在网络中的位置
- 随着网络设备的增加, 32位的 IP 地址呈现不够用的姿态, 产生了 动态分配, NAT, IPv6 等手段
1) 网段划分
根据子网掩码, 全1,(即255)为网络号, 全0,(即0)为主机号
但是为每一个子网中的主机设置不同的网络号和主机号是一件繁琐的事情, 所以路由器中就有 “DHCP” 这种功能, 能够自动为每一个子网中的主机进行分配 IP 地址.
2) 解决 IP 地址不够用
-
动态分配
因为全世界的设备不是同时都是上网状态, 比如中国是白天的时候, 美国就是黑夜, 这时美国上网的设备会明显减少, 所以就将之前分配给美国的 IP 地址在他们上网设备大量减少的时候, 分配给中国的设备进行使用.
但是这种方法并不能从根本上解决, 而且带来的提升十分有限.
-
NAT
这是一种装在路由器上进行使用的软件, 能够对于同一子网中的主机进行 IP 地址映射, 这样就可以使得一个 IP 地址为大量的主机所使用.
过程:
但是这种方法并不能从根本上解决, 而且带来的提升有限, 较 “动态分配” 提升较大
-
IPv6
这种方式从根本上解决了 IP 地址不够用的问题, 但是由于与 IPv4并不兼容, 所以想要使用 IPv6 上网, 需要更换全部的硬件, 所以目前并不是主流, 尽管在我国已经占了半壁江山, 但是流量并不是很大.
Ipv6使用了 32 位进行表示 IP 地址, 可以为地球上的每一粒沙子分配一个 IP 地址
3) 私有 IP 地址 & 公网 IP 地址
IP 地址在 NAT 机制的划分下, 分为 私网 与 公网.
私网:
公网:
4) 特殊 IP
127.* : 环回IP, 测试使用的 IP
主机号全0: 表示==“这个网段”==
主机号全1: 表示==“广播地址”==, 也就是这个网络号中的所有设备都能够收到这个地址发来的消息
5) 网络选择
网络选择的过程就像地图软件不普及时的 问路, 是对于一个目的地的探索性询问, 一次询问可能问不出来终点位置, 但是每一次的询问都使自己更加接近终点
-
牵扯出了 MAC 地址, 这个地址描述的是数据报在每个结点之间走到的地址, 是一个==“区间地址”==
-
每个路由器对于自己周围的网络环境有一个基本的认识, 维护一张表, 就像一个区域的居民对于本区域的地标性建筑有一定的认识, 使得外地人来问路可以给出基于当前地点的模糊性方向传输层网络协议