一、常见名词解释
IP地址:IP地址主要用于标识网络主机、其他网络设备(如路由器)的网络地址,即IP地址用于定位主机的网络地址;
IP地址是一个32位的二进制数,通常被分割为4个 8位⼆进制数(也就是4个字节),通常用 "点分十进制" 的方式来表,即 a.b.c.d 的形式(a,b,c,d都是0~255之间的十进制整数) 如:100.4.5.6;
端口号:在网络通信中,IP地址用于标识主机网络地址,端口号可以标识主机中发送数据、接收数据的进程。 简单说:端口号用于定位主机中的进程;
协议:在网络上传输的数据包的格式;
OSI 七层模型:
物理层、数据链路层、网络层、传输层、会话层、表示层、应用层;
TCP/IP 五层网络模型:
- 物理层:描述的是网络通信的硬件设备,集线器(Hub)工作在物理层;
- 数据链路层:负责设备之间的数据帧的传送和识别,交换机(Switch)工作在数据链路层;
- 网络层:负责地址管理和路由选择,路由器(Router)工作在网路层,IP 协议是在网络层;
- 传输层:负责两台主机之间的数据传输,TCP,UDP 协议是传输层的协议;
- 应用层:负责应用程序间沟通;对应 OSI 中的 会话层、表示层、应用层;
服务端:在常见的网络数据传输场景下,把提供服务的一方进程称为服务端,可以提供对外服务;
客户端:获取服务的一方进程,称为客户端 ;
二、TCP 和 UDP 的区别(*)
- TCP 是有连接的,UDP 是无连接的;
- TCP 是可靠传输,UDP 是不可靠传输;
- TCP 是面向字节流的,UDP 是面向数据报的;
- TCP 和 UDP 都是全双工的;
三、UDP 协议(User Datagram Protocol)
端口号(Port) 标识了⼀个主机上进行通信的不同的应用程序,0 - 1023: 知名端口号,HTTP(80),FTP(21),SSH(22),HTTPS(443) 等这些广为使用的应用层协议,他们的端口号都是固定的,当我们自己写一个程序使用端口号时,要避开这些知名端口号;
UDP 数据报格式
报头:源端口号,目的端口号,UDP 报文长度,UDP 校验和,四部分每部分 2 个字节;
UDP 采用 CRC 循环冗余算法计算校验和(本质是字符串,由原始数据生成),如果校验和出错,就会直接丢弃当前数据报;
UDP 报文长度也占两个字节 0 ~ 65535,也就是说一个 UDP 数据报能传输的数据最大长度是 64K (包含 UDP 首部),如果我们需要传输的数据超过 64K,就需要在应用层手动的分包,多次发送,并在接收端手动拼装;
计算校验和的其他算法(了解):MD5 算法,特点
- 定长:无论输入的消息有多长,MD5 总是生成一个固定长度为128位(16字节)的哈希值;
- 分散:哪怕只有一个字符不同,算出的 MD5 值的差异都会很大;
- 不可逆:MD5 算法是不可逆的,意味着不能从哈希值逆推出原始输入。这种特性使得它适合于密码存储等应用
基于 UDP 的应用层协议:NFS 网络文件系统协议,TFTP 简单文件传输协议,DHCP 动态主机配置协议,DNS 域名解析协议;
四、TCP 协议(Transmission Control Protocol)
TCP 数据报格式
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
- 32 位序号:用于标识每个字节在数据流中的位置。TCP将每个字节的数据流进行编号,序号是发送方为每个字节分配的唯一编号,TCP连接建立时,双方会选择一个随机的初始序号;
- 32 位确认序号:确认序号用于告诉发送方接收方已经成功接收到的数据的下一个期望字节序号;
- 4 位首部长度:表示该 TCP 头部有多少字节,TCP头部最短为 20 字节最长为 60 字节(选项占 40 字节);
- 6 位标志位: URG:紧急指针是否有效 ACK:确认号是否有效 PSH:提示接收端应用程序立刻从 TCP 缓冲区把数据读走 RST:复位报文段,对方要求重新建立连接 SYN:同步报⽂段,请求建立连接; FIN:结束报⽂段,通知对方,本端要关闭了;
- 16 位窗口大小:表示接受缓冲区的剩余空间大小;
- 16位校验和:发送端填充,CRC校验,接收端校验不通过,则认为数据有问题,此处的检验和不只包含 TCP 首部,也包含 TCP 数据部分;
- 16位紧急指针:标识哪部分数据是紧急数据;
TCP 实现可靠传输的方式:(*)
1. 确认应答(TCP核心)
确认应答机制:对发送方发送的每⼀个数据段,接收方都要给一个 ACK 来确认应答,发送方收到ACK 后再发送下一个数据段;
TCP为每个数据段分配一个序号,该序号表示数据段中的第一个字节在整个数据流中的位置;
接收方通过特殊的 ack 数据包,里面携带的 "确认序号" 告诉发送方,哪些数据已经被收到了;发送方通过 ack 位是否为 1,判断当前数据包是否为应答报文,若为 1 则是应答报文,确认序号字段生效,若为 0,则是普通报文,确认序号字段不生效;
例如上图中,接收方发送的确认序号为 1001,则表示 1001 之前的数据已经全部接收到,并且期望接收到的下一个数据的序号为 1001,发送方看到确认序号为 1001,就知道该继续发送序号为 1001 的数据;
2. 超时重传
当发送方长时间(超时重传时间)没有收到接收方的 ack,即出现数据丢包的情况(发送的数据丢包或返回的 ack 丢包),重新传输发送的数据包;
TCP 由一个接收缓冲区,会保存当前已经接收到的数据和数据的序号,当接收方收到的数据在缓冲区中存在时,接收方会把后来的数据丢掉,可以防止重传时,接收方收到大量的重复数据,
超时重传时间的确定:TCP 动态调整超时时间,以适应网络条件的变化,超时时间通常基于往返时间(RTT,Round-Trip Time)估计,发送方通过测量发送数据段到接收确认的时间,估计RTT,并根据 RTT 计算超时时间。
3. 连接管理(建立连接 + 断开连接)
三次握手(建立连接)
建立连接完成之后,A 和 B 记录了对方的信息,构成了逻辑上的连接;若建立连接失败,如果客户端还想重新建立连接,就需要发送 RST 报文;
三次握手解决的问题
- 确认当前网络状况是否畅通,进行确认应答和超时重传的前提是网络环境是基本可用的;
- 让接收方和发送方确认自己的发送能力和接收能力是否均正常;(类比连麦时,测试耳机和麦克风是否都正常);
- 让通信双方在握手过程中,针对一些重要的参数进行协商(例如序号从几开始); 可以避免当网络不好的时候,客户端和服务器断开连接,再重新建立新的连接后,上个连接的数据姗姗来迟,而这种迟到的数据应该丢弃,即可通过序号的设定避免该情况 ;
四次挥手(断开连接)
四次挥手能否优化为三次(经典面试题)
答:不能,因为 第一个 ACK 和第二个 FIN 的触发时机不同,ACK 是内核响应的,B 收到 FIN 会立即返回 ACK,第二个 FIN 是应用程序代码触发的,B 调用了 close 方法才会触发 ACK,其次,TCP 协议设计的关键是可靠传输,简化其中任何一步都会增加数据丢失或连接状态不同步的风险;特别是在网络不稳定的情况下,分开的 ACK 和 FIN 包可以更有效地处理重传和确认问题;
4. 滑动窗口 (缩短确认应答的响应时间)
由于延迟应答机制中,发送方需要等待接收方回复 ack 报文之后再继续发送数据,这样效率很低,而滑动窗口机制保证了可以一次发送多条数据,这样就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)
窗口大小:指的是无需等待确认应答时,可以继续发送数据的最大值;窗口越大,网络的吞吐率就越高,传输效率就越高,但如果窗口大小设置的太快,接受方可能会接收不过来;
操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答,只 有确认应答过的数据,才能从缓冲区删掉;
此时若出现丢包情况:
1. ACK 丢包
此时不需要重传,确认序号发挥作用,表示当前序号之前的数据已经全部接收到;
2. 数据包丢包
此时接收方会将 2001 - 3000 的数据放在接收缓冲区中,当补收到 1001 - 2000 的数据时,下次可直接接收 3001 开始的数据;这种机制也叫做 快速重传机制;
5. 流量控制
接收方处理数据的速度是有限的,如果发送方发的太快,导致接收方的缓冲区被用满,这个时候如果发送方继续发送,就会造成丢包,继而引起重传等等一系列连锁反应;因此需要在接收方的角度,制约发送方的传输速率,这就称为流量控制;
接收方将自己可以接收的缓冲区大小放入 TCP 首部中的 "16 位窗口大小" 字段,通过 ACK 通知发送方;窗口大小字段越大,说明网络的吞吐量越高,接收方一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送方,发送方接受到这个窗口后,就会减慢自己的发送速度;
如果接收端缓冲区满了,接收方就会将窗口置为 0,这时发送方不再发送业务数据,但是需要定期发送⼀个窗口探测数据段,用来询问接收方的剩余缓存区空间;
窗口大小为 2 个字节,16位数字最大表示 65535,那么TCP窗口最大就是65535字节吗?
实际上,TCP首部的 40 字节选项中还包含了一个窗口扩大因子 M,实际窗口大小是窗口字段的值左移 M 位;
6. 拥塞控制
虽然 TCP 有了滑动窗口,能够高效可靠的发送大量的数据,但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题,因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的;
拥塞控制:先让发送方按照比较低的速率(拥塞窗口)发送数据,如果没有丢包,则一点一点的将拥塞窗口调大,当出现丢包时,发送方就会把拥塞窗口调小;
每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;
7. 延迟应答
正常情况下,发送方发送数据之后,接收方收到数据会立即返回 ACK 给发送方,此时返回的窗口可能比较小;
延迟应答:当接收方收到数据后,不会立即返回 ACK 给发送方,而会等一会,给接收方更多的时间接收读取缓冲区的数据,接收方读取的更多,缓冲区剩余空间就越大,返回的窗口大小就越大,发送方下次能发送的数据就越多(本质是提升传输效率);
那么所有的包都可以延迟应答么?
当然不是,数量限制:每隔 N 个包就应答一次;时间限制:超过最大延迟时间就应答一次;具体的数量和超时时间,依操作系统不同也有差异,⼀般 N 取 2,超时时间取 200 ms;
8. 捎带应答
在延迟应答的基础上,很多情况下,客户端服务器在应用层也是 "一发一收" 的,意味着客户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine thank you",那么这个时候 ACK 就可以搭顺风车,和服务器回应的 "Fine thank you" 一起回给客户端;
9. 面向字节流
创建一个 TCP 的 socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区;
调用write时,数据会先写入发送缓冲区中;
如果发送的字节数太长,会被拆分成多个 TCP 的数据包发出;
如果发送的字节数太短,就会先在缓冲区⾥等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去;
接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区;
然后应用程序可以调用read从接收缓冲区拿数据;
另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这一个连接,既可以读数据, 也可以写数据,这个概念叫做全双工;
由于缓冲区的存在,TCP 程序的读和写不需要一一匹配,例如: 写100个字节数据时,可以调用一次 write 写 100个字节,也可以调用 100 次 write,每次写一个字节; 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次 read 100 个字节,也可以一次 read 一个字节,重复 100 次;
面向字节流存在的问题:粘包(应用层数据包)问题
在应用层的角度:接收方不知道缓冲区的数据,从哪到哪是一个完整的应用层数据包;
解决思路:明确两个包之间的边界;
对于定长的包,保证每次都按固定大小读取即可;
对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置,或者可以在包和包之间使用明确的分隔符;
10. 对应异常情况的处理
- 进程终止 / 崩溃:进程终止会释放文件描述符,相当于调用 socket.close()方法,此时仍然可以发送 FIN,和正常断开连接(四次挥手)没有什么区别;
- 主机关机:此时会先触发强制终止进程,然后和 1 类似,但如果在在系统关闭之前,对端的 FIN 和 ACK 迟到或丢失,则关机方已经关机,无法回应 ACK,在对端尝试重传几次之后,仍然没有回应,此时对端也就放弃连接了;
- 主机掉电 / 网线断开:瞬间的,来不及终止进程; 1)接收方掉电:发送方一直等待 ACK,触发超时重传仍无响应之后,会引起 TCP 连接重置功能,发送方发起 RST 复位报文段,仍无响应后,发送方单方面释放连接; 2)发送方掉电:心跳包机制,接收方周期性的给发送方发起一个特殊的数据包,并期待应答,重复多次之后仍没有应答,接收方会单方面释放连接;
五、TCP 和 UDP 的对比及应用场景
1. TCP
优点:可靠传输,流量控制,连接管理等;
缺点:开销较大,不适用于广播和多播;
应用场景:适用于大多数场景,如:文件传输(如FTP),电子邮件(如SMTP),远程登录(如SSH、Telnet),Web浏览(如HTTP、HTTPS)
2. UDP
优点:低开销,低延迟,天然支持广播和多播;
缺点:不可靠传输,无连接管理;
应用场景:实时视频和音频流(如VoIP、视频会议),广播和多播应用(投屏功能),域名系统(DNS)等;
投屏功能:手机和电视在同一局域网下,手机触发投屏功能,发起广播数据包,询问电视是否接收投屏,电视回应确认后,把 ip,端口号告诉主机后,实现投屏功能;
六、如何基于 UDP 实现可靠传输?
参考 TCP 的可靠性机制,在应用层实现类似的逻辑,如引入序列号,确认应答机制,超时重传机制,滑动窗口机制等;