Linux知识点 – 网络基础 – 传输层
一、传输层协议
1.端口号
端口号标识了一个主机上进行通信的不同的进程;
在TCP/IP协议中,用源IP、目的IP、源端口号、目的端口号、协议号这样一个五元组来标识一个通信;
2.网络相关bash命令
二、UDP协议
1.UDP报文的解包与交付
-
UDP报文格式:
-
UDP协议报文的解包:
UDP的报文是固定长度的,分离时直接将报头提取出来,留下有效载荷;
-
UDP协议报文的交付:
报文中提供了目的端口号,根据此16位端口号向上交付,进程bind了端口号;
-
应用层编写代码时,端口号是uin16_t类型的,这是因为UDP协议中的端口号是16位的;
-
UDP如何提取完成报文:
UDP报头中有16位UDP长度,且报头是固定长度,因此根据长度就能提取完整报文;
2.理解UDP报文
UDP报文都是使用位段实现的,位段就是对象,是能够拷贝的;
封装报头:内核定义报头对象,将用户数据拷贝到内核缓冲区,将报头对象拷贝到用户数据前面;
3.UDP协议的特点
- 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接;
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息;
- 面向数据报:不能够灵活的控制读写数据的次数和数量;
应用层交给UDP多长的报文,UDP原样发送,不会拆分和合并;
4.UDP应用层IO类接口
- sendto/recvfrom/write/read/recv/send …IO类接口其实不是在网络中进行数据的收发,而是从内核层缓冲区中拷贝数据,或者拷贝到内核层缓冲区;
- 缓冲区都是传输层提供的;
- 数据的发送细节都是由UDP协议控制的,什么时候发,一次发多少,出错怎么办,都是由操作系统处理的;
5.UDP的缓冲区
- UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作;
- UDP具有接收缓冲区,但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃;
- UDP的socket既能读也能写,这个概念叫做全双工;
6.UDP使用注意事项
UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部);
然而64K在当今的互联网环境下,是一一个非常小的数字,如果我们需要传输的数据超过64K,就需要在应用层手动的分包,多次发送,并在接收端手动拼装;
7.基于UDP的应用层协议
NFS:网络文件系统
TFTP:简单文件传输协议
DHCP:动态主机配置协议
BOOTP:启动协议(用于无盘设备启动)
DNS:域名解析协议
三、TCP协议
1.TCP报文的解包与交付
- TCP报文结构:
- TCP协议交付:
TCP通过目的端口号决定报文交付的进程;
TCP报头由固定长度的属性(20字节)和选项(最多40字节)构成,因此报头长度为]20,60]字节;
4位首部长度:表示TCP报头长度的属性,表示的大小为0-15,单位是4字节,最终能表示20-60字节的报头长度,因此4位首部长度的取值范围是5-15;
- TCP协议解包:
(1)提取20字节;
(2)根据标准报头,提取4位首部长度 * 4 = 20 - done;
(3)读取 [提取4位首部长度* 4- 20] 字节数据,选项;
(4)读完了报头,剩下的都是有效载荷;
注意:TCP报头中没有有效载荷的大小;
2.理解TCP协议的报文
TCP报文也是一个位段结构,与UDP类似;
3.理解TCP协议的可靠性
在操作系统单机内部,我们不谈协议,但是已到了网络层面,就需要用到协议,这是因为距离变长了,数据的可靠性就下降了;
网络中不存在100%可靠的协议,任一方都无法保证自己作为数据发送方发送的所有数据都被对方接收;
但是,在局部上,我们可以做到100%可靠:
我们发出去的消息,只要有匹配的应答,就能够保证对方一定接收到了;
因此,TCP协议的确认应答机制:只要一个报文收到了对应的应答,就能保证发出的数据被对方接收到了;
4.序号和确认序号
实际TCP的通信过程中,不是一个报文发送了,接收到该信号的应答后再发送下一条报文,而是一次发送多条报文,这样接收方接收到的报文有可能是乱序的;
TCP报头中的序号和确认序号就是用来帮助接收端确认报文顺序的:
- 将请求和应答进行一一对应;
- 确认序号 – 表示的含义:确认序号之前的数据已经全部收到,下次发送从确认序号之后的序号发送;
- 允许部分确认丢失,或者不给应答;
- 为什么要有两个字段数字?
任何通信的一方,工作方式都是全双工的,在发送确认的时候,也可能携带新的数据;
5.TCP的缓冲区
TCP有发送缓冲区和接收缓冲区,并且是全双工通信;
收发数据都是将数据拷贝到缓冲区,交给操作系统,细节流程都是由TCP协议控制;
6.16位窗口大小
TCP协议发送的数据都是先暂存在对方主机的传输层缓冲区上的,缓冲区是有大小限制的,因此发送数据的一方,不能发太快,也不能发太慢,也就是说发送的时候要进行流量控制;
- 那么依据什么进行流量控制呢?
一台主机的接受能力,是由接收缓冲区中剩余空间的大小来决定的,16位窗口大小就是己方主机接收缓冲区剩余空间大小,里面填的是自己主机的剩余缓冲区大小,发送给对方主机,给发送方同步自己的接受能力,让其知道我们的接收缓冲区还剩多少空间;
7.6个标记位
- SYN: 该位置1,表明该报文是一个链接请求报文;
- FIN:该位置1,表明该报文是一个断开链接请求报文;
- ACK:确认应答标志位,凡是该报文具有应答特征,该标志位都会被设置为1;大部分网络报文ACK都是被设置为1的;
- RST:该位置1,表明该报文是一个链接重置请求报文;
连接未建立成功或出现异常,想让对方关闭连接,服务端向客户端回复报文,将RST标志位置位,客户端就会将连接关闭; - PSH:该位置1,催促对方尽快将数据进行向上交付;
检测到对方窗口大小为0,督促对方交付数据; - URG:紧急标志位,表明该报文高优先级交付;
与URG功能对应的还有16位紧急指针;
- 16位紧急指针:指向的是报文中紧急数据在有效载荷中的偏移量,当URG置位时有效,通常是用来进行机器管理;
8.TCP的建立连接(三次握手)
因为有大量的client将来可能链接server,所以server端一定会存在大量的连接;
OS要不要管理这些连接?先描述,再组织!
所谓的连接:本质其实就是内核的一种数据结构类型,建立连接成功的时候,就是再内存中创建对应的连接对象,再对多个连接对象进行某种数据结构的组织;
维护连接是有成本的(内存 + cpu)
- 理解三次握手:
服务端调用listen接口后,进入LISTEN监听状态,并同时调用accept接口,等待连接;
第一次:客户端调用connect接口,向服务端发送SYN报文,请求连接,客户端变为SYN_SENT状态;
第二次:服务端在收到客户端建立链接请求后,向客户端发送SYN + ACK报文,建立链接并应答,服务端变为SYN_RCVD状态;
第三次:客户端在收到服务端建立链接和应答报文后,即connect接口成功返回后,向服务端回复ACK报文,客户端变为ESTABLISHED状态;服务端收到客户端应答后,即accept接口成功返回,服务端置为ESTABLISHED状态;
注:
- 为什么是三次握手?
由于维护链接是有成本的,如果是一次或者两次握手,客户端与服务端承受的资源损失是不一致的,客户端可以向服务端发送大量SYN请求,丢弃服务端的ACK,这样服务端会挂载大量链接,而客户端不会;
三次握手可以嫁接同等的成本给客户端;
三次握手也可以验证收发双方的全双工; - 三次握手是否一定能够成功?
不一定的,当客户端将第三次的ack发出的时候,客户端就置为ESTABLISHED状态,服务端是不一定能收到的;
9.TCP的断开连接(四次挥手)
- 理解四次挥手:
第一次:客户端在调用close接口关闭fd后,向服务端发送FIN报文,请求关闭连接,客户端置为FIN_WAIT_1状态;
第二次:服务器在收到客户端FIN报文后,进入CLOSE_WAIT状态,准备关闭连接,并向客户端发送ACK应答;客户端在收到ACK后,进入FIN_WAIT2状态;
第三次:服务端在处理完之前的数据后,调用close接口关闭连接,并向客户端发送FIN报文,服务端进入LAST_ACK状态,等待最后一个ACK的到来;
第四次:客户端在收到服务端FIN报文后,进入TIME_WAIT状态,并向服务端发送ACK报文,TIME_WAIT状态会持续一段时间,之后客户端进入CLOSED状态;服务端在收到ACK报文后,彻底关闭连接,进入CLOSED状态;
注:
- 如果发现服务端连接出现了大量的CLOSE_WAIT状态,原因是什么?
服务端应用层代码有bug,没有关闭对应的sockfd; - 四次挥手能否保证一定成功断开连接?
不一定; - 服务器可以将ACK和FIN合并在一个报文发送,因此有时也会称为三次挥手;
- 主动断开连接的一方,其与对方的连接会进入TIME_WAIT状态一段时间;
如果服务端直接杀死进程,由于文件描述符是跟随进程的,因此该文件描述符也会关闭,连接也会断开,进入TIME_WAIT状态;
此时,服务器维持TIME_WAIT状态是无法立即重启的,需要等待连接变成CLOSED状态;
它的错误码是3,绑定错误;
这是因为在TIME_WAIT状态下,连接其实已经释放,但是地址信息ip、port依旧是被占用的,因此再次绑定端口是无法绑定的;
TIME_WAIT等待时间为2 MSL(Max Segment Life,报文最大生存时间),保证历史数据从网络中消散;
等待时间内,如果客户端发出的ACK应答丢失,对方一定会重新发送FIN报文,那么客户端就能够知道ACK丢失了,可以进行补发;
如果对方没有重新发FIN,证明ACK一定送达了; - 为什么TIME. WAIT的时间是2 MSL?
MSL是TCP报文的最大生存时间,因此TIME_ WAIT持续存在2 MSL的话:
●就能保证在两个传输方向上的尚未被接收或迟到的报文段都已经消失(否则服务器立刻重启,可能会收到
来自上一个进程的迟到的数据,但是这种数据很可能是错误的);
●同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失,那么服务器会再重发一个FIN,这
时虽然客户端的进程不在了,但是TCP连接还在,仍然可以重发LAST_ ACK);
- 服务器挂掉后需要有立即重启的能力:
使用setsockopt接口设置listensocket的属性,设置其地址复用功能,这样服务器就会绕过TIME_WAIT状态的判断,直接让其可以绑定成功;
设置地址复用后,即使服务器在TIME_WAIT期间,也可以继续绑定;
10.确认应答机制 – 可靠性机制
只要保证A发送数据时,B都有应答,就可以保证A向B发送时的可靠性;
发出去的报文称为数据段,包括报头和数据;
- TCP发送缓冲区:
可以将TCP的发送缓冲区看作一个char sendbuffer[NUM]的数组的结构,发送的数据段在发送缓冲区天然就有序号,序号就是数组的下标;
11.超时重传机制 – 可靠性机制
当一个数据段发出后发送端主机在特定的时间间隔内没有收到该数据段的确认应答,TCP协议就会触发超时重传,再次发送该数据段;
注:
- 需要设定超时时间间隔,超出时间间隔若对方未回复,会进行重传;
超时时间不能太长,也不能太短,需要根据网络状况来确认;
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间:
●Linux中(BSD Unix和Windows也是如此),超时以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍;
●如果重发一次之后,仍然得不到应答,等待2500ms后再进行重传;
●如果仍然得不到应答,等待4500ms进行重传,依次类推,以指数形式递增;
●累计到一定的重传次数,TCP认为网络或者对端主机出现异常,强制关闭连接; - 重传有两种情况:发送的报文丢失,对方的确认应答丢失;
12.流量控制 – 可靠性机制
接收端处理数据的速度是有限的,如果发送端发的太快,导致接收端的缓冲区被打满,这个时候如果发送端继续发送,就会造成丢包,继而引起丢包重传等等一系列连锁反应;
因此TCP支持根据接收端的处理能力,来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control);
- 接收端将自己可以接收的缓冲区大小放入TCP首部中的“窗口大小”字段,通过ACK端通知发送端;
- 窗口大小字段越大,说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端;发送端接受到这个窗口之后,就会减慢自己的发送速度;
- 如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端;
注:
- 在进行流量控制的时候,第一次发送方是如何知道接收方的接受能力的?
第一次发送数据并不是双方第一次交换报文,在TCP三次握手的时候,双方就已经完成报文交换了,双方的窗口大小也通过报头告诉对方了; - 在接收端的就收缓冲区大小有空余后,也会向发送端发送一个窗口更新通知;
13.滑动窗口 – 提高效率机制
刚才我们讨论了确认应答策略,对每一个发送的数据段都要给一个ACK确认应答,收到ACK后再发送下一个数据段;
这样做有一个比较大的缺点就是性能较差,尤其是数据往返的时间较长的时候;
如果我们一次并行发送多条数据段,就可以大大提高性能;
TCP可以并行发送多个报文,但一定要在对方主机的缓冲区接受能力内,滑动窗口机制就是用来实现这种发送策略;
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值;上图的窗口大小就是4000个字节(四个段);
滑动窗口的大小 = min(拥塞窗口, 接收端窗口的大小) - 滑动窗口左端都是已经发送且收到应答的数据段;右端都是尚未发送的数据段;滑动窗口中的数据段可以直接发送,且暂时不需要应答;
- 收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推;
- 操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;
- 窗口越大,则网络的吞吐率就越高;
- 滑动窗口在发送方的发送缓冲区中,属于发送缓冲区的一部分;
- 滑动窗口的本质:发送方可以一次性向对方推送数据的上限;
- 滑动窗口必须有上限,与对方的接受能力有关;
- 滑动窗口机制的目的是:既想给对方推送更多的数据,又想要保证对方来得及接收;
深入理解滑动窗口:
-
滑动窗口其实是由两个指针维护的,在发送缓冲区中,存在两个指针win_start和win_end,这两个指针之间就是滑动窗口的内容;
-
滑动窗口的更新策略:win_start = 收到的应答报文中的确认序号,win_end = win_start + min(拥塞窗口, 接收端窗口的大小);
-
滑动窗口不一定必须右移,如果接收方不取数据,那么滑动窗口左指针会右移,而右指针不动;
-
滑动窗口大小是可以为0的,如果对方一直不取数据,可能会导致滑动窗口大小为0;
-
如果没有收到滑动窗口开始报文的应答,而是收到中间报文的应答,有影响吗?
不影响,确认应答序号的作用就是收到的确认应答序号之前的数据全部已经接收,即使前面的应答报文丢失,也可以通过后续的应答进行确认(只要发送的报文是没有丢失的);
-
超时重传的意义:就是在发送的数据没有收到应答的时候,必须暂时保存起来;
-
快重传机制 – 提高效率机制
当某一段报文段丢失之后,发送端会一直收到 1001这样的ACK,就像是在提醒发送端“我想要的是1001"一样;
如果发送端主机连续三次收到了同样一个"1001"这样的应答,就会将对应的数据1001 - 2000重新发送;
这个时候接收端收到了1001之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中(通过报文的序号来确认是否在接收端已保存);
-
滑动窗口如果一直向右滑动,会不会存在越界问题?
不会,因为TCP的发送缓冲区是环状的,相当于一个环形数组;
-
超时重传与快重传
两者不是对立的,是协作的,因为快重传是有条件的;两者联合起来,实现网络的高效重发;
-
在少量丢包的时候,采用重传机制;
在大量丢包的时候,有可能是网络拥塞的问题,这时是绝对不能重传的,如果全部主机都重传,则会导致网络拥塞更加严重;
14.拥塞控制 – 可靠性机制
虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据,但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题;
因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵,在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引|起雪上加霜的;
TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;
- 此处引入一个概念程为拥塞窗口;
拥塞窗口:单台主机一次向网络中发送大量数据时,可能会引发网络拥塞的上限值;只是一个整数; - 发送开始的时候定义拥塞窗口大小为1;
- 每次收到一个ACK应答,拥塞窗口加1;
- 每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送的窗口;
- 因此,滑动窗口的大小 = min(拥塞窗口, 接收端窗口的大小);
像上面这样的拥塞窗口增长速度,是指数级别的;“慢启动”只是指初使时慢,但是增长速度非常快;
- 为了不增长的那么快,因此不能使拥塞窗口单纯的加倍;
- 此处引入一个叫做慢启动的阈值;当拥塞窗口超过这个阈值的时候不再按照指数方式增长,而是按照线性方式增长;
当TCP开始启动的时候,慢启动阈值等于窗口最大值;
在每次超时重发的时候慢启动阈值会变成原来的一半,同时拥塞窗口置回1; - 少量的丢包,我们仅仅是触发超时重传;大量的丢包,我们就认为网络拥塞;
- 前期要让网络有一个缓一缓的机会少,慢,解决网络拥塞问题;
中后期,网络恢复了之后,尽快恢复通信的效率;
当TCP通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降;
拥塞控制,归根结底是TCP协议想尽可能快的把数据传输给对方但是又要避免给网络造成太大压力的折中方案;
15.延迟应答 – 提高效率机制
- 如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小;
假设接收端缓冲区为1M; 一次收到了500K的数据,如果立刻应答,返回的窗口就是500K;
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了;
在这种情况下,接收端处理还远没有达到自己的极限,即使窗口再放大一些,也能处理过来;
如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口大小就是1M;这就是延迟应答机制;
窗口越大,网络吞吐量就越大,传输效率就越高;我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
- 那么所有的包都可以延迟应答么?
肯定也不是;
数量限制:每隔N个包就应答一次;
时间限制:超过最大延迟时间就应答一次;
具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms
16.捎带应答 – 提高效率机制
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是”一发一收"的;意味着客户端给服务器说了"How are you",服务器也会给客户端回一个"Fine, thank you";
那么这个时候ACK就可以搭顺风车,和服务器回应的"Fine, thank you" 一起回给客户端;
发送应答报文的时候,有可能也会携带数据,就是将数据和应答合并了,这就是携带应答;
17.TCP机制总结
18.面向字节流
TCP协议会在内核中同时创建一个发送缓冲区和一个接收缓冲区;
发送方应用层发送数据时,只管将数据写入到自己的发送缓冲区;接收方应用层读取数据的时候,只管从自己的接收缓冲区中读取数据;
数据什么时候从发送方发送到接收方,一次发送多少数据,都是由操作系统通过TCP协议来控制的;
UDP报文发送和接收都是一对一的,一定保证接收到一个完整报文;
- 由于缓冲区的存在,TCP程序的读和写不需要一一匹配,这就是面向字节流,例如:
写100个字节数据时, 可以调用一次write写 100个字节,也可以调用100次write,每次写一个字节;
读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read 100个字节,也可以一次read一个字节,重复100次;
文件流也是一样的道理,也是字节流,数据拷贝到内核缓冲区的时候,OS是不关心写入的格式和大小,用户也不关心OS是如何刷新缓冲区的;
收发双方的读写策略都是不相关的;
19.粘包问题
首先要明确,粘包问题中的"包",是指的应用层的数据包;
在TCP的协议头中,没有如同UDP一样的“报文长度"这样的字段,但是有一个序号这样的字段;
站在传输层的角度,TCP是一个个报文过来的,按照序号排好序放在缓冲区中;
站在应用层的角度,看到的只是一-串连续的字节数据;
那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包,这就是粘包问题;
- 如何避免粘包问题:
关键在于:明确两个包之间的边界;
对于定长的包,保证每次都按固定大小读取即可;
对于变长的包,可以在包头的位置约定一个包总长度的字段,从而就知道了包的结束位置;
对于变长的包,还可以在包和包之间使用明确的分隔符;
20.TCP异常
- 进程终止:进程终止会释放文件描述符(释放文件描述符就是正常四次挥手),仍然可以发送FIN,和正常关闭没有什么区别;
- 机器重启:和进程终止的情况相同;
- 机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset;即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在;如果对方不在,也会把连接释放;
21.TCP和UDP对比
- TCP用于可靠传输的情况应用于文件传输,重要状态更新等场景;
- UDP用于对高速传输和实时性要求较高的通信领域例如,早期的QQ,视频传输等;另外UDP可以用于广播;
22.用UDP实现可靠传输
- 引入序列号,保证数据顺序;
- 引入确认应答,确保对端收到了数据;
- 引入超时重传,如果隔一段时间没有应答,就重发数据;