网络原理 - TCP/IP
文章目录
传输层
两个问题:
- ⼀个端口号是否可以被多个进程bind?
同一个机器上,同时刻内,端口号不能被同一个协议下的进程重复绑定
如何确认,在当前机器上,某个端口号是否被进程使用了呢?
我们运行以下代码:
import java.net.DatagramSocket;
import java.net.SocketException;
public class Server {
public static void main(String[] args) throws SocketException {
DatagramSocket socket = new DatagramSocket(9090);
while(true);
}
}
在控制台输入netstat -ano | findstr 9090
, 这个命令就是查询当前主机是否有使用9090端口的进程
20816就是绑定了该端口的进程的PID,我们可以在任务管理器查看
为什么有两行呢?
这是因为绑定端口号的时候,没有做特殊指定。就把IPv4和IPv6两个协议的9090都给绑定了。此时,这个UDP服务器程序,客户端可以使用IPv4来访问也可以使用IPv6来访问。
运行以下代码再查看9090端口号绑定情况,同一个端口号是可以同时被不同协议绑定的
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.ServerSocket;
public class Server {
public static void main(String[] args) throws IOException {
DatagramSocket udpSocket = new DatagramSocket(9090);
ServerSocket tcpSocket = new ServerSocket(9090);
while(true);
}
}
2. ⼀个进程同一时刻是否可以bind多个端口号?
可以的
运行以下代码:
import java.io.IOException;
import java.net.ServerSocket;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket tcpSocket = new ServerSocket(9090);
ServerSocket tcpSocket2 = new ServerSocket(9091);
while(true);
}
}
这是一个进程,使用多个ServerSocket绑定了多个端口
UDP协议
UDP协议报文格式:
-
源端口号就是发送方UDP程序的端口号
-
目的端口号就是接收方UDP程序的端口号
-
UDP长度是两个字节, 范围是0~65535,单位是bit, 换算之后就表示整个数据报(UDP报头+UDP载荷)最大为64KB, 也就是说⼀个UDP能传输的数据最大长度是64KB(包含UDP首部),是有限制的
-
校验和/检验和是用来判断数据是否在传输过程中出错,如果校验和/检验和出错, 就会直接丢弃这个数据
数据在网络传输过程中可能"出错",比如"比特翻转",二进制数据中的0变成了1,或者1变成了0。数据在网线,光纤中分别就是电信号,光信号,可能受到外界环境的干扰
这就需要对传输的数据进行校验
第一层:能够发现是否出错
第二层:最好能发现是哪一位出错,并且能进行纠错(相比于第一层消耗的时间/空间更多)在UDP中,校验和只能做到第一层,使用CRC的方式计算校验和,通过发送方和接收方的UDP校验和是否相同来判断是否有错
UDP最主要的用途:应用于对于性能要求比较高,但是对于可靠性要求不高的场景
TCP协议
TCP全称为 传输控制协议(Transmission Control Protocol)
TCP协议报文格式:
-
源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去
-
4位首部长度: 表示该TCP头部大小是多少(单位是4字节); 所以TCP头部最大长度是15 *4 = 60个字节,注意指的是报头的长度,而不是TCP报文总的长度
-
保留位是预留的空间,说不定以后用来扩展功能
-
6位标志位:
- URG: 紧急指针是否有效
- ACK: 确认号是否有效
- PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
- RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
- SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
- FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
-
16位校验和: 和UDP一样是CRC校验。接收端校验不通过, 则认为数据有问题。 此处的检验和不光包含TCP首部, 也包含TCP数据部分
-
16位紧急指针: 标识哪部分数据是紧急数据;
-
选项:这里的内容可以选择加或不加,如果不加选项,TCP报头长度就是20字节; 又因为TCP报头最大长度是60字节,所以选项最大可以是40字节
TCP协议的核心机制
确认应答机制
TCP协议要解决一个很重要的问题:可靠传输
所谓的可靠传输,不是说一定能把数据完整地发送给对方,但是会尽可能,尤其是能够让发送方知道,接收方是否收到。发送方发送数据后接收方就会返回一个应答报文来表示自己收到了
用于应答的报文称为"应答报文",这样的回应就是确认应答。应答(acknowledge)的英文缩写就是ack,如果标志位ack为1,说明这就是一个应答报文,为0则不是
在网络传输的过程中,经常会出现一个情况:后发先至,这是客观存在的改变不了。只要能够对传输的数据进行编号,并且让应答报文的编号和发送的数据的编号能够对应起来。即使出现后发先至也可以纠正顺序
- 报头中的32位确认序号是给应答方(应答报文)使用的(ack为1的时候才有效),这样数据就可以根据确认序号区分出要应答哪个报文了
- 报头中的32位序号是给发送方使用的
TCP的序号是按照"字节"来编号的,每个字节的数据都会分配一个序号,每个字节的编号都是连续递增的。报头中的32位序号就是载荷部分第一个字节的序号,由于序号连续递增的特点,只要知道第一个字节的序号,后续每个字节的序号就都知道了
而每⼀个应答报文都带有对应的确认序号, 意思是告诉发送方, 我已经收到了哪些数据; 下⼀次你从哪里开始发。确认序号的取值就是要应答的数据的最后一个字节的序号+1
假设每个数据载荷都是1000字节
如果序号超过32位,就会重新协商起始序号
超时重传机制
用来应对丢包的情况(因为网络拥堵等原因, 数据无法到达)
正常情况下,TCP是通过 确认应答 来知道数据是否被对端接收到。如果应答方没收到数据,那就不会给出任何应答;或者应答方接收到数据给出了应答,但是应答丢包了。发送方就根据是否接收到了应答来区分是否出现丢包。如果发送方在⼀个特定时间间隔内没有收到应答方发来的确认应答就认为是丢包了,就算超过时间之后收到应答也认为是丢包了, 就会进行重发
如果是数据成功发送到了,但是应答丢包了触发了重传。这样接收方就收到了两份重复的数据。所以就需要进行去重
接收方的操作系统内核中存在接收缓冲区。当数据层层分用到TCP这一层时会被放入这个接收缓冲区中,放的过程中,会根据当前数据的序号,判定这个数据是否在队列中存在(或者曾经在队列中存在过),只要存在过,这个数据就会被直接丢弃,不过即使判定出现重复了也会继续返回应答报文,不然发送方继续会认为自己没发送成功而重传
接收缓冲区,除了去重之外,还有一个很重要的功能:针对收到的数据进行排序,并且数据和数据之间的序号始终是连续的,如果后面的数据先到,队首会留有空位等待前面的数据到达。这样就算后发先至,数据也能有序地到达接收方
超时时间不是固定数值,而是动态变化的。随着重传轮次的增加,会变得越来越长。重传达到一定次数时就会尝试"重置连接",也就是触发一个"复位报文"(标志位RST为1)尝试重置连接,重置就是通信双方清空之前TCP传输过程中的中间状态(比如缓冲区的数据都不要了),如果重置失败,那就只能断开连接(释放掉保存对端信息的数据结构)
超时重传是确认应答的重要补充
TCP可靠传输全靠 确认应答 和 超时重传 这两个机制支撑
连接管理
在正常情况下, TCP要经过三次握⼿建立连接, 四次挥⼿断开连接
在握手和挥手的过程中,传输的网络数据报不携带任何业务上的数据,只是传输了一些单纯为了建立/断开连接的数据
三次握手
建立连接就是通信双方各自保存对端的信息,完成上述过程就需要经过三次网络交互
上述流程,客户端和服务器各自给对方发送SYN,再各自给对方返回一个ACK。其实是4次交互,但中间的两次交互ACK和SYN可以合并成一个网络数据
“三次握手"不能变成"二次握手”(缺少客户端给服务器的ACK),这样服务器就不知道自己的发送能力和客户端的接收能力是否正常
能变成"四次握手",但是没必要,性能会有损失
三次握手就相当于双方让对方保存自己的信息,只有两边都把对方的信息保存好,连接才算建立完成
三次握手的意义:
-
投石问路, 确认当前通信路径是否畅通.
-
确认通信双方发送和接收能力都是正常的
-
协商参数, 通信双方共同确认⼀些通信中的必备参数数值
TCP通信时使用的序号就是协商出来的。第一次连接和第二次连接协商出来的起始序号往往差异很大,因为TCP考虑到一个情况,避免第一次连接发的数据传输了很久,第一次连接都断开了,第二次连接建立了才传输到。服务器就会根据序号区分出来是不是当前连接的客户端传输的数据,如果序号相差很大就直接丢弃了
四次挥手
- 三次握手,一定是客户端发起第一次请求
- 四次挥手则不一定,客户端和服务器都可以主动发起
代码中调用socket.close方法或者进程结束就会触发四次挥手,四次挥手之后才真正执行删除对方信息的操作
四次挥手中间的两次能否合并? 如能。特殊情况下能合并,正常情况下不能合并
为啥不能合并?
- 三次握手过程中,SYN和ACK的发送时机都是内核控制的,可以同时发送
- 四次挥手过程中,由于这两个数据报触发是不同的时机,因此就难以进行合并。只有在特殊情况下,上述两个数据可以合并,TCP还有一个机制:延时应答(不是立刻回复ACK,而是等一会),在这种情况下,就可以合并
面试题: 如果在报错中见到大量的CLOSE_WAIT,说明代码很可能忘记调用close或者调用close不及时
- CLOSE_WAIT不一定是服务器处于的状态,而是被动接受的一方
- TIME_WAIT不一定是客户端处于的状态,而是主动发起的一方
TIME_WAIT的状态等待对方重传FIN,等待的时间一般是2MSL,MSL是TCP报文的最大生存时间(也是可配置的),也就是2MSL之后还没有收到重传的FIN就认为对方不会重传了
面试题:如果发现服务器出现大量的TIME_WAIT,是什么情况?
出现大量TIME_WAIT,说明服务器这边触发了大量的主动断开TCP连接的操作
滑动窗口
发送一次数据段, 就要给⼀个ACK确认应答, 等收到ACK后再发送下一个数据段。 这样做的话性能较差, 尤其是数据往返的时间较长的时候
TCP除了保证可靠传输之外,也希望尽可能高效的完成数据传输,滑动窗口就是一种提高传输效率的机制
不引入滑动窗口的数据传输过程:
主机A收到ACK才发送下一个数据,比较低效。既然这样一发一收的方式性能较低, 那么我们一次发送多条数据, 就可以大大的提高性能(其实是将多个数据段的等待时间重叠在⼀起了)
引入滑动窗口之后的效果:
批量发送把等待的时间重叠了,提高了传输效率
"窗口大小"指的是无需等待确认应答(ACK)可以发送数据的最大值。超过窗口大小就不再发送直到收到ACK,不然可靠性难以保证。只要接收到滑动窗口中任意一个数据的ACK,确认序号之前的窗口部分就空出来了,就可以继续接收数据了。 上图的窗口大小就是3000个字节
- 发送前3000字节的时候, 不需要等待任何ACK, 直接发送
- 收到ACK后, 滑动窗口向后移动, 发送方继续发送之后的数据; 依次类推
- 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉
- 窗口越大, 则网络的吞吐率就越高
那么如果出现了丢包, 如何进行重传? 这里分两种情况讨论
- 数据包已经抵达,ACK丢了
这种情况不需要任何处理。只要后面的ACK传递到了(这说明前面的数据都传到了),前面的ACK丢包了也不要紧
- 数据包丢了
主机A一边一直重传缺失的部分,一边传输后面的数据直到窗口的阈值,窗口放不下了主机A就暂停发后面的数据,直到主机B确认接收到了缺失的数据,把滑动窗口向后面的序号移动了
-
当1001~2000丢失之后, 发送端会⼀直收到 1001 这样的ACK, 就像是在提醒发送端 “我想要的是1001” 一样
-
这个时候接收端收到了 1001~2000 的数据之后, 再次返回的ACK就是6001了,因为 2001 - 6000 的数据接收端之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中
这种机制被称为 “高速重发控制”(也叫 “快重传”),这也是滑动窗口下,搭配的丢包处理机制
上述针对丢包的处理是很高效的
- 对于ACK丢失,不做任何处理
- 对于数据丢失,只需要把缺失的数据重传即可,其他的数据不必重传
超时重传和快速重传不冲突。快速重传相当于超时重传在滑动窗口下的特殊变种
- 当TCP传输的数据比较少,此时不会触发滑动窗口。按照超时重传方式来解决丢包问题
- 如果是短时间传输大量的数据,此时才会触发滑动窗口。此时触发快速重传
不过就算滑动窗口提升的效率再高,速度也不可能比UDP这样没有可靠机制的协议更快
流量控制
可以通过报头的16位窗口大小控制发送方的发送速度。窗口越大,单位时间发的数据就越多,效率就越高;窗口越小,单位时间发的数据就越少,效率就越低
所以我们希望窗口尽可能大,但发送速度太快接收方处理不过来还可能引起丢包,所以TCP根据接收端的处理能力, 来决定发送端的发送速度。 这个机制就叫做流量控制(Flow Control)
如何衡量接收方的处理速度呢?
接收方有一个接收缓冲区,把空闲的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段(只在ACK报文中生效), 通过ACK通知发送端; 虽然窗口大小只有16位,但窗口大小最大不是64KB,报头的选项中包含特殊属性:窗口扩大因子,相当于左移运算符
- 窗口大小字段越大, 说明网络的吞吐量越高;
- 发送端接收到这个窗口大小之后, 就会根据窗口大小更改自己的发送速度;
- 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送⼀个窗口探测数据段, 使接收端把窗口大小告诉发送端.
假设接收缓冲区是4000字节,下面没考虑B消费数据的过程
拥塞控制
和流量控制类似,都是和滑动窗口搭配的机制
当前的网络状态可能比较拥堵,在不清楚当前网络状态的情况下, 贸然发送大量的数据, 是很有可能引起大量丢包的。链路上任何一个节点的性能瓶颈都会制约发送方的发送速度
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 如果没有出现丢包(说明中间链路非常畅通),就可以指数增加窗口大小,直到出现丢包就会减少窗口大小(缩减到出现丢包时窗口大小一 半这样的位置,接下来线性增长),最终找到一个合适的窗口大小(不丢包,并且以较快速度完成传输)
少量的丢包, 仅仅是触发超时重传; 大量的丢包, 就认为网络拥塞
“慢启动” 只是指一开始慢, 但是增长速度非常快。拥塞控制, 归根结底是TCP协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案
流量控制,拥塞控制,取较小的值作为实际发送的窗口大小
延迟应答
ACK不会立即返回,而是稍等一会再返回,就是腾出来更多的消费时间
如果接收数据的主机立即返回ACK应答, 这时候返回的窗口可能比较小
- 假设接收端缓冲区为1000KB。一次收到了500KB的数据; 如果立即应答, 返回的窗口就是500KB
- 但实际上接收端处理的速度很快, 可能10ms之内就把500KB数据从缓冲区消费掉了
- 在这种情况下, 接收端远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来
- 如果接收端稍微等一会再应答, 这时返回的窗口大小就会大很多
⼀定要记得, 窗口越大, 网络吞吐量就越大, 传输效率就越高。我们的目标是在保证网络不拥塞的情况下尽量提高传输效率
那么所有的包都可以延迟应答么? 肯定也不是,有两种方式(结合使用的):
• 数量限制: 每隔N个包就应答⼀次;
• 时间限制: 超过最大延迟时间就应答⼀次;
具体的数量和超时时间, 依操作系统不同也有差异
捎带应答
在延迟应答的基础上提升效率的机制
很多情况下, 客户端服务器在应用层是 "一发一收"的
正常来说,ACK是收到请求后自动返回的,响应是应用程序代码执行一系列逻辑之后返回的
由于延时应答的存在,ACK不一定立即返回,在ACK返回的时候正好把响应数据带上,这样就提高了效率
面向字节流
创建⼀个TCP的socket的时候, 会在内核中创建⼀个 发送缓冲区 和⼀个 接收缓冲区
- 调用write时, 数据会先写入发送缓冲区中
- 如果发送的字节数太长, 会被拆分成多个TCP的数据包发出
- 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去
- 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区
- 然后应用程序可以调用read从接收缓冲区拿数据
另一方面, TCP的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以写数据,也可以读数据。这个概念叫做 全双工
由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:
- 写100个字节数据时, 可以调用一次write写100个字节, 也可以调用100次write, 每次写⼀个字节
- 读100个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100个字节, 也可以一次read一个字节, 重复100次
粘包问题
- 首先要明确, 粘包问题中的 “包” , 指的是应用层的数据包,主要是区分从哪到哪是完整的应用层数据包,明确包的界限
- 站在传输层的角度, TCP是一个一个报文过来的。按照序号排好序放在缓冲区中.
- 站在应用层的角度, 看到的只是一串连续的字节数据.
- 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包
那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界,下面是一些解决粘包问题的方式:
- JSON,xml,yml使用分隔符
- protobuf约定包的长度
- http既用到长度,也用到分隔符
思考: 对于UDP协议来说, 是否也存在 “粘包问题” 呢?
- UDP是把一个一个数据完整地交付给应用层, 就有很明确的数据边界
- 站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收, 不会出现"半个"的情况
异常情况
- 进程终止(进程崩溃或者正常结束): 进程终止会释放文件描述符, 相当于调用close,虽然进程不在,操作系统仍然可以正常地和对方进行四次挥手
- 主机正常关机: 会正常进行四次挥手,但可能在挥手流程走完之前就关机了, 这时对方重传了几次FIN没有ACK就会断开连接了(把关机电脑的信息删除)
- 主机掉电/网线断开:
- 假设是接收端的主机掉电, 发送端接下来收不到ACK,就会触发超时重传,重传了几次之后就会发送复位报文(RST)。RST也没有响应发送端就会删除接收端的信息
- 假设是发送端的主机掉电,接收端在一定时间收不到任何信息,不知道对方是暂停一会还是挂了,就会触发心跳包。心跳包是一个没有载荷的数据包,只是为了触发ACK。如果回应了ACK就说明连接正常;连续发了若干次没有回应,就会认为对方挂了,就会断开连接
TCP虽然内置了心跳包,但是周期比较长,应用层的某些协议, 也有一些这样的检测机制,用更高频率,更短周期发送心跳
小结
为什么TCP这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能.
- 可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
- 提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
- 其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)
用UDP实现可靠传输(经典面试题)
参考TCP的可靠性机制, 在应用层实现类似的逻辑
例如:
• 引入序列号, 保证数据顺序;
• 引入确认应答, 确保对端收到了数据;
• 引入超时重传, 如果一段时间没有应答, 就重发数据;
• …
网络层
在复杂的网络环境中确定一个合适的路径
- 路径规划(路由选择)
- 地址管理
IP协议
- 主机: 配有IP地址, 但是不进行路由控制的设备;
- 路由器: 即配有IP地址, 又能进行路由控制;
协议头格式:
- 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4
- 4位首部长度(header length): 表示IP报头长度,也是以4字节为单位, 所以IP报头最长就是60字节,最短就是20字节(选项无内容)
- 8位服务类型(Type Of Service): 3位优先权字段(已经弃用), 4位TOS字段, 和1位保留字段(必须置为0)。4位TOS分别表示: 最小延时(传输时间最短), 最大吞吐量(单位时间传输的数据尽可能多), 最高可靠性(降低丢包的概率), 最小成本(比较节省系统开销)。这四者相互冲突, 只能选择⼀个为1
- 16位总长度(total length): IP数据报整体(报头+载荷)占多少个字节。IP实现了拆包组包的功能,如果报文超过长度64KB上限,IP就会自动拆分成多个数据包,每个数据包携带一部分发送到对方之后,再拼接好
下面三个属性用来实现IP的拆包组包,这个过程完全是IP本身负责的,和载荷中的东西是TCP还是UDP没有关系
- 16位标识(id): 唯一标识主机发送的报文,用来区分哪些数据包要进行合并。 如果IP报文在数据链路层被分片(拆包)了, 那么每一个片里面的这个id都是相同的。
- 3位标志字段: 只有两个有效。第一位保留(保留的意思是现在不用, 说不定以后要用到)。 第二位用来判断该数据包是否触发了拆包(是否组包)。第三位类似于一个结束标记,表示当前包是否就是最后一个需要组包的部分
- 13位片偏移(framegament offset): 是分片相对于原始IP报文开始处的偏移。其实就是在表示当前分片在原报文中处在哪个位置。记录要拼接的数据包的先后顺序
- 8位生存时间(Time To Live, TTL): 数据报到达目的地的最大报文跳数。 一般是64, 每次经过一个路由就减1, 一直减到0还没到达, 那么就丢弃了。 这个字段主要是用来防止出现路由循环
- 8位协议: 表示上层协议的类型,载荷是哪种协议的数据包
- 16位头部校验和: 使用CRC进行校验, 来鉴别头部是否损坏。只是针对IP首部进行校验,不关心载荷部分,载荷部分UDP/TCP都有各自的校验和
- 32位源地址和32位目标地址: 表示发送端和接收端
- 选项字段: 不定长, 最多40字节
为啥不能借助IP组包拆包的能力突破UDP长度限制
因为发送或者解析的时候,最大就是64KB,虽然能传很大,但是不符合UDP协议就解析不了
IP地址不够用的问题
原则来说,每个设备的IP地址应该是唯一的,但32位最多只能表示42亿9千万个设备,不太够用。
三个解决方案:
一、动态分配IP地址
上网就分配,不上网就不分配
二、 NAT机制(网络地址映射)
虽然没有增加IP地址的数量,但是极大提高了IP地址的复用情况,使用一个公网IP代表一组设备
首先把IP地址分成两个大类:
-
私网/内网IP(局域网内部使用), 下面三种情况都是私网ip,剩下的都是公网ip
- 10.* (前8位是网络号,共16,777,216个地址)
- 172.16-172.31.*(前12位是网络号,共1,048,576个地址)
- 192.168.*(前16位是网络号,共65,536个地址)
-
公网/外网IP(广域网使用)
约定公网IP唯一,私网IP允许在不同局域网中重复。虽然现在上网的设备很多,但绝大部分都是在局域网中的
如果使用上述私网IP,如何进行通信呢?
- 同一个局域网内部,设备之间正常通信
- 广域网设备和广域网设备之间正常通信
- 一个局域网内的设备不能直接访问另一个局域网内的设备。但是可以通过广域网的服务器进行数据转发
- 局域网设备可以主动访问广域网设备
- 广域网设备不能主动访问局域网设备
运营商路由器可以认为是一个NAT设备,它会对中间经过的数据包进行网络地址转换。当内网设备经过运营商路由器访问外网的时候,就会把IP数据包的源IP替换成它自己的IP,这样就变成外网访问外网了,是可以进行访问的
这是没有考虑端口的情况, 端口有两种情况:
- 端口不重复
可以根据端口区分出不同的请求方,端口号不用改变
- 端口重复
当内网设备经过运营商路由器访问外网的时候,运营商路由器就会给数据包的源端口重新分配一个不重复的端口。所以端口号有可能不够用,要把运营商路由器划分片区,让每个运营商路由器管理一部分地区的设备。设备也可能在不同的局域网,只要归同一个运营商路由器管,端口号重复就会重新分配端口号,并且记录下映射关系
手机开热点就是手机变成路由器,连同一个热点就是在同一个局域网下
当前网络世界,就是通过 动态分配+NAT 的方式解决的IP不够用问题
三、使用IPv6
IPv6使用16个字节表示IP地址,足以给地球上每一粒沙子分配一个唯一的IP,所以不会不够用。 IPv6并不是IPv4的简单升级版, 这是互不相干的两个协议, 彼此并不兼容。
为什么现在还是IPv4为主呢?因为IPv4和IPv6不兼容,如果要升级成IPv6就得更换成支持IPv6的路由器/交换机/网卡,而NAT不需要更换设备,只要更新一下程序就行了,成本非常低
地址管理
网段划分
同一个局域网的主机,要按照一定的规则分配IP地址
把一个IP地址(IPv4)分成两个部分
- 前半部分:网络号=>标识局域网
- 后半部分:主机号=>区分同一个局域网的不同主机。
同一个局域网内的主机, 网络号相同,主机号不同。不能说网络号相同就在同一个局域网,因为不相邻的局域网的网络号可以相同。但是,两个相邻的局域网网络号不能相同; 一旦相邻的局域网网络号相同了,就没法上网了
一个IP地址32位,怎么区分哪些位是网络号,哪些位是主机号呢?
就引入了子网掩码,也是32位整数,左半部分都是1,右半部分都是0。IP地址对应子网掩码为1的部分是网络号,为0的部分是主机号
将IP地址和子网掩码进行 “按位与” 操作, 得到的结果就是网络号
从子网掩码看出前三个字节(192.168.137)标识网络号,后一个字节( 1 )是主机号
这样搭配子网掩码的网段划分是现在的方法
手动管理子网内的IP, 是一件相当麻烦的事情.
- 有一种技术叫做DHCP, 能够自动的给子网内新增主机节点分配IP地址, 避免了手动管理IP的不便
- 一般的路由器都带有DHCP功能, 因此路由器也可以看做一个DHCP服务器。一个路由器可以配置两个IP地址, 一个是WAN口IP, 一个是LAN口IP(子网IP)。路由器LAN口连接的主机, 都从属于当前这个路由器的子网中
过去曾经提出⼀种划分网络号和主机号的方案, 把所有IP 地址分为五类, 没有子网掩码。网络号主机号的位数都是固定的,如下图所示:
• A类 0.0.0.0到127.255.255.255
• B类 128.0.0.0到191.255.255.255
• C类 192.0.0.0到223.255.255.255
• D类 224.0.0.0到239.255.255.255
• E类 240.0.0.0到247.255.255.255
随着网络的飞速发展,这种划分方案的局限性很快显现出来,大多数组织都申请B类网络地址, 导致B
类地址很快就分配完了, 而A类却浪费了大量地址
IP地址和子网掩码还有一种更简洁的表示方法,例如140.252.20.68/24,表示IP地址为140.252.20.68, 子网掩码的高24位是1,也就是255.255.255.0
特殊的IP地址
- 将IP地址中的主机号全部设为0, 就成为了网络号, 代表这个局域网;这样的IP地址不应该分配给具体的主机
- 将IP地址中的主机号全部设为1, 就成为了广播地址。用于给同一个链路中相互连接的所有主机发送数据包;一个应用场景就是设备发现,找到可以投屏的对象,投屏要求设备在同一个局域网内
- 127.*的IP地址用于本机环回(loop back)测试,通常是127.0.0.1(往这个IP发送数据,就是自己来接收,即使不联网也可以使用)
路由选择
在复杂的网络结构中, 找出较优的一条通往终点的路线
路由的过程, 是一跳一跳(Hop by Hop) “问路” 的过程
- 当IP数据包, 到达路由器时, 路由器会先查看目的IP
- 每个路由器内部维护了一个路由表。如果目的IP在路由表中, 直接转发即可;当目的IP地址不在路由表中,就按规定的接口发送到下一跳地址
- 依次反复, 一直到达目标IP地址
数据链路层
认识以太网
- “以太网” 不是一种具体的网络, 而是一种技术标准(协议); 既包含了数据链路层的内容, 也包含了一些物理层的内容。平时使用的有线网络属于以太网
- 以太网是当前应用最广泛的局域网技术; 和以太网并列的还有令牌环网, 无线LAN等
以太网的帧格式如下所示:
- 源地址和目的地址是指源主机和目标主机的网卡的硬件地址(也叫MAC地址), 长度是48位
- 帧协议类型字段有三种值,分别对应IP、ARP、RARP
- 帧末尾是CRC校验码
一个以太网数据帧的数据部分最大长度其实就是1500字节。以太网自身不能拆包组包,当IP数据包长度达到1500字节就会触发拆包组包了,以确保以太网数据帧能装得下
ARP/RARP都不会携带业务上的载荷数据,只是对转发数据的过程起到"辅助"效果
MAC地址(物理地址)是网卡出厂的时候设置好的,不能修改。原则上来说,每个网卡的物理地址都是不相同的
- MAC地址用来识别数据链路层中相连的节点
- 长度为6个字节,目前来说够用,因此可以认为每个设备都有唯一的MAC地址(虚拟机中的MAC地址不是真实的MAC地址, 可能会冲突; 也有些网卡支持用户配置MAC地址)。 一般用16进制数字加上冒号的形式来表示(例如: 08:00:27:03:fb:19)
对比MAC地址和IP地址:
目的都是为了区分网络上不同的设备
- IP地址描述的是整个路途的 起点 和 终点(相当于长期规划),IP层规划了路径,决定下一个节点是谁
- MAC地址描述的是路途上的两个相邻节点(相当于短期规划),已经知道了下一个节点, 决定该怎么去下一个节点
MTU
- 规定以太网帧中的数据长度最小46字节,最大1500字节
- 最大值1500称为以太网的最大传输单元(MTU)
- 如果一个数据包从以太网路由到拨号链路上,数据包长度大于拨号链路的MTU了,则需要对数据包进行分片(fragmentation)/拆包
- 不同的数据链路层标准的MTU是不同的
应用层
应用层协议,本质上就是对传输的数据做出约定
- 约定传输的数据要有哪些信息
- 传输的数据要遵守啥样的格式
因为我们自己的数据是结构化数据,网上传输的是二进制字符串/字符串,所以需要序列化。
序列化的方式:
-
基于行文本的方式来传输(自定义分隔符使用哪些,怎么分割),可维护性差,可读性差
-
基于xml的方式(用成对的标签来组织数据),Maven通过xml来管理配置。
html可以理解成一种特殊的xml
-
基于JSON,这是当前最流行,最广泛的使用方式。可读性非常好,而且比xml简洁
用
{}
作为边界,{}
里面是键值对,键值对之间使用,
分割,键和值之间使用:
分割 -
基于yml
通过缩进来表示包含/嵌套关系,对于缩进是强制要求的,可读性也很好
-
基于Protobuf(pb)
前面几种都是文本格式,能看懂,pb则是二进制格式了,针对要传输的数据进一步整理和压缩。虽然可读性不好,但能把空间最充分地利用,最节省网络带宽,效率也最高
应用层也有一些现成的协议供直接使用,其中最典型的代表就是HTTP协议
DNS
应用层协议DNS(Domain Name System),也就是域名解析系统
DNS是一整套从域名映射到IP的系统
浏览器中输入url后, 发生的事情。这是一个经典的面试题, 没有固定答案, 越详细越好。可以参考:
当你在浏览器地址栏输入一个URL后回车,将会发生的事情?
IP地址不方便记忆,于是发明了域名,也就是一个字符串来表示某个/某组IP地址。以前有一个文件,专门用来维护域名和IP的映射关系,这个文件至今存在,而且还有效
我的电脑在这个路径下:C:\Windows\System32\drivers\etc\hosts
可以用记事本打开hosts文件,这个文件就是用来解析域名的。以前很有用,现在几乎不使用了。因为每天都有新网站出现,也有旧网址消亡,导致这个文件就需要频繁更新,而且还占空间。取而代之的则是搭建了DNS服务器,把IP地址与域名的映射关系使用DNS服务器来保存
如果要访问域名,就可以通过访问DNS服务器查询到对应的IP地址
这么多设备都要使用DNS服务器,DNS服务器怎么支持这么大的请求呢?
- 客户端缓存
电脑会记住之前访问过的域名与IP的映射关系,下次再访问就不需要重新向DNS服务器查询
- 分布式的方式
世界各地建设了很多的DNS镜像服务器,此时,请求DNS服务器只需要就近访问附近的镜像服务器就行了
根服务器是镜像服务器的源头。如果是想申请域名搭建网站,就需要把域名和IP的映射提交到根服务器, 然后镜像服务器就会从根服务器这边同步数据。
当前域名体系是"分级"的体系
比如这个域名: pic.sogou.com
- com是一级域名
- sogou是二级域名
- pic是三级域名
查询的过程是先查一级域名,再是二级域名,三级域名…
原文地址:https://blog.csdn.net/2302_77179144/article/details/141786638
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!