传输层
传输层提供进程和进程之间的逻辑通信
端口号:标识一台主机上唯一进程
在TCP/IP协议中,用五元组标识一个通信:源IP、目的IP、源端口号、目的端口号、协议号
UDP
1.UDP特点
- 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接(减少开销和发送数据之间的时延);
- 不可靠:没有确认机制,没有重传机制;不保证可靠交付;如果因为网络故障该段无法发送到对方,UDP协议层也不会给应用层返回任何错误信息;
- 面向数据报:应用层交给UDP多长的报文,UDP原样发送,既不会拆分也不会合并,不能够灵活的控制读写数据的次数和数量;
- UDP首部有一个16位UDP长度,表示整个数据报(UDP首部+UDP数据)的最大长度,也就是一个UDP能传输的数据最大长度是64K,如果超过64K就需要在应用层手动分包,多次发送,并在接收端手动拼装;
2.UDP的缓冲区
UDP有接收缓冲区,但不保证收到的UDP报顺序和发送的一致,如果缓冲区满了,再到达UDP数据就会丢弃;
3.基于UDP的应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议
- DNS:域名解析协议
TCP协议
1.TCP特点
- 面向连接,在应用进程建立通信之前,首先TCP需要建立传输数据所需的连接;
- 每一条TCP连接只能有两个端点,即点对点;
- TCP是可靠有序的,不丢包不重复
- 能够对数据进行分割重组:相对于UDP,应用层只需将数据发给TCP协议,TCP协议自己能将数据进行分割,在接收端TCP能够进行重组;
- 面向字节流:TCP把应用层的数据堪称无结构的字节流,发送方TCP会把数据放入缓冲区等到能发送的时候发送,数据块太长TCP可以将它划分短一些再发送,也可以积累足够多的字节再构成报文段发送,TCP可以根据当前网络拥塞状态确定报文段大小;
TCP如何实现可靠性?
什么是可靠性?
1.数据发送没有错误
2.发送方知道接收方收到了数据
3.接收到的数据是有序的
4.如果接收到重复数据,接收方是知道的
怎么解决?
- 校验和机制:发送端填充,CRC校验,接收端校验不通过则认为数据有问题;
- 确认应答:TCP协议规定,当接收方接收到数据要向发送方进行确认,即回一个ACK,如果发送方没有收到接收方的确认,就会把未确认的数据进行重发;
- 序列号:发送端的TCP协议将发出的每个字节的数据都进行了编号.,即为序列号,这样在接收端TCP协议就能够对数据进行重新排序,确保了数据的有序传输,并且接收方也能知道是否有重复数据;
2.TCP三种机制
确认应答机制
TCP的通信过程中,需要保存当前连接(Connection)的本地发送序列号,以及对方的确认序列号,保存在接收缓冲区;
TCP有一个标志位ACK,每一个ACK都带有对应的确认序列号,可以告诉发送者我已经接收到了哪些数据,下次你从哪里开始发;
比如客户端发送了1-1000,服务器返回给客户端确认序号1001,客户端就会从1001开始发;
超时重传机制
TCP有发送缓冲区,可以实现数据重发。
- 主机A发送数据给B之后, 可能因为网络拥堵等原因数据无法到达主机B,如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发;
- 但是主机A没收到确认应答也可能是ACK丢失了;因此主机B可能会收到很多重复数据,用序列号就能做到去重的效果;
- TCP为了保证任何环境下都能保持较高性能的通信, 因此会动态计算这个最大超时时间.;累计到一定的重传次数,,TCP认为网络或者对端主机出现异常,强制关闭连接;
连接管理机制
TCP是有状态的,TCP栈需要知道当前状态,所以需要描述连接当前的阶段,正常情况下,TCP需要经过三次握手建立连接,四次挥手断开连接;
- 具体的三次握手过程:
C->S 1. syn:主动连接方(客户端)发送Segment(段/报文)
目的:告诉对方——被动连接方(服务器),主动连接方的初始序列号
S->C 2. syn+ack:被动连接方(服务器)发送Segment(段/报文)
目的:告诉对方——主动连接方(客户端),被动连接方的初始序列号
C->S 3. ack:确认应答对方的上一个Segment
每个连接两端,初始序列号是随机的(为了防止网络攻击(伪造包)),需要一开始双方先沟通互相的初始序列号;
1.刚开始, 客户端和服务器都处于 CLOSED 状态,此时 客户端向服务器主动发出连接请求, 服务器被动接受连接请求;
1.服务器端调用listen后进入LISTEN(监听)状态,时刻准备接受客户端进程的连接请求;
2, TCP客户端进程也是先创建传输控制块TCB, 然后服务器发出连接请求报文,此时报文首部中的同步报文段SYN=1, 初始序列号 seq = x ;此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态;SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号;建立连接时双方先沟通互相的初始序列号;
3, TCP服务器 一旦监听到连接请求(同步报文段), 就将该连接放入内核等待队列中, 并向客户端发
送SYN确认报文;确认报文中的 ACK=1, SYN=1, 确认序号是 x+1, 同时自己也初始化一个序列号 seq = y, 此时, TCP服务器进程进入了SYN-RCVD(同步收到)状态,这个报文也不携带数据;
4, TCP客户端进程收到确认后,客户端进入ESTABLISHED(已建立连接)状态,客户端要向服务器发送确认报文ACK=1,确认序号是 y+1,自己的序列号是 x+1.
5.TCP连接建立,当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
客户端(CLOSED->SYN_SENT->ESTABLISHED)
服务器(CLOSED->LISTEN->SYN_RCVD->ESTABLISHED)
为什么不是两次?
两次不安全,SYN可能会延迟,客户端没有收到服务器的确认报文就会重发这条报文建立连接,然后发送数据关闭连接,此时可能之前滞留的SYN到达了服务器,两次握手会让本该失效的报文再次让客户端和服务器建立连接,收到一个SYN就会建立一个socket;
C 发 SYN,S 回 ACK,S 发 SYN,C 回 ACK,确认应答总共需要这四个过程,23可以合并,两次不满足确认应答;
为什么不是四次?
能少尽量少,四次多余了,因为 S 回 ACK 和 S 发 SYN 肯定是同时的,所以合并了;
四次挥手:
1.客户端发送连接释放报文,FIN=1,序列号seq=x(前面传送序号+1),客户端进入FIN_WAIT_1(终止等待1)状态;
2.服务器接收连接释放报文,回ACK=1,确认序号x+1,自己的序号y,此时服务器进入CLOSE_WAIT(关闭等待)状态,客户端收到服务器的确认后进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文,还可以接收服务器发送的数据;
3.服务器发送完最后的数据,就向客户端发送连接释放报文,FIN=1,确认序号y+1,序列号z,服务器进入LAST-ACK(最后确认)状态,等待客户端确认。
4.客户端收到FIN后发出ACK=1,确认序号w+1,序号x+1,客户端进入TIME-WAIT(时间等待)状态;此时,TCP连接还没有释放,必须经过2*MSL时间(保温最大生存时间)后,当客户端撤销相应TCB才进入CLOSED状态;服务器只要接收到了客户端的确认就立即进入CLOSED状态
TIME_WAIT状态
MSL是TCP报文最大生存时间,TIME_WAIT持续存在2MSL的话:
1.保证两个传输方向上未被接收到的报文段都已经消失;客户端发送完最后一个确认报文后,在这个2MSL时间中,可以使本连接持续的时间内所产生的所有报文段都从网络中消失,新的连接中不会出现旧连接的请求报文;(否则服务器立刻重启,可能会收到来自上个进程迟到的数据,但这种数据可能是错误的,设置TIME_WAIT,报文最多迟到的时间是2MSL)
2.同时也是在理论上保证最后一个报文可靠到达(假设最后一个ACK丢失, 那么服务器会再重发一个FIN,客户端就能在2MSL时间段内收到重传的报文,这时虽然客户端的进程不在了, 但是TCP连接还在, 仍然可以重发ACK);
如果服务器出现大量CLOSE_WAIT状态:
原因就是服务器没有正确的关闭 socket,导致四次挥手没有正确完成.,只需要加上对应的close即可解决;
为什么关闭连接是四次挥手?
关闭连接时,服务器收到对方的FIN报文时,仅仅表示客户端不再发送数据了但是还能接收数据,而自己有可能还有数据未发送,所以服务器可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此服务器ACK和FIN一般都会分开发送,从而导致多了一次;
如果已经建立了连接, 但是客户端突发故障了怎么办?
TCP设有一个保活计时器,服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次;若发送10个探测报文仍没反应,服务器就会认为客户端出了故障然后关闭连接;
3.滑动窗口
滑动窗口是描述发送方这边的发送缓冲区
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,发送前几段不需要等待ACK直接发送,收到第一个ACK后滑动窗口向后移动,再继续发送数据;
操作系统内核为了维护这个滑动窗口需要开辟发送缓冲区记录还有哪些数据没有应答,只有确认应答过的数据才能从缓冲区删掉;窗口越大,则网络吞吐率越高;
如果出现丢包?
1.ACK丢失:部分ACK丢失并没有影响,可以通过后续的ACK进行确认;每一条确认回复中的确认序号,都要保证之前的数据已经完全收到;假如没有收到第一条回复但收到了第二条回复,认为第一条和第二条都已经正确传输,避免因为ack丢失而导致的数据重传;
2.数据包丢失:当某一段报文丢失后,发送端发送其他的数据会一直收到同样的ACK,当连续三次收到同样一个ACK应答,就会将对应重新数据发送,在重复发送ACK之间收到的发送端发送的数据接收端已经收到了,被放在接收端操作系统内核的接收缓冲区;(高速重发控制,也叫快重传)
4.流量控制
接收端处理数据的速度是有限的,发送端发送太快会导致接收端缓冲区被打满,这时候继续发送会造成丢包;
流量控制就是TCP支持根据接收端的处理能力来决定发送端发送速度;
接收端将自己可以接收的缓冲区大小放入TCP首部“窗口大小”字段,通过ACK告知发送端;
如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端;
窗口大小字段越大说明网络的吞吐量越高;
5.拥塞控制
TCP有一个慢启动机制,先发少量的数据,当清楚当前网络拥堵状态再决定按照多大速度传输数据
发送开始的时候,定义拥塞窗口大小为1,每次收到一个ACK应答拥塞窗口加1;
发送方的发送窗口取:拥塞窗口和接收端主机反馈的窗口大小中较小的值
像这样的拥塞窗口增长速度是指数级别的,慢启动只是起初慢但增长非常快,因此有了慢启动的阈值:当拥塞窗口超过这个阈值,就不按指数增长而是线性增长;
当TCP开始启动,慢启动阈值等于窗口最大值,在每次超时重发的时候,慢启动阈值会变成原来的一半,拥塞窗口置1;
拥塞控制就是TCP协议想尽可能快的把数据传给对方但又要避免给网络造成太大压力;
少量的丢包触发超时重传大量丢包则认为是网络拥塞,TCP通信开始网络吞吐量上升,发生网络拥堵吞吐量会立刻下降;
6.延迟应答机制
窗口越大则网络吞吐量越大传输效率越高,接收方接收数据若是立即回复,则窗口大小会降低,会导致传输速度降低;因此接收方接收到数据之后,并不立即回复,而会延迟一会(不超过500毫秒),在这期间用户可能将数据从接收缓冲区中取出,可以尽量保证窗口大小,保证传输速度不会降低;
那么所有的数据包都可以延迟应答么?
不是,有两个限制
数量限制: 每隔N个包就应答一次
时间限制: 超过最大延迟时间就应答一次
具体的数量和超时时间, 依操作系统不同也有差异
7.捎带应答机制
接收方每次要对收到的数据进行确认回复,还要发送数据,如果单单发送一个TCP报头是不划算的,将要进行的确认回复和将要发送的数据合到一起进行发送,就可省略一个tcp报头的发送,减少网络中不必要的流量;
8.面向字节流
创建一个TCP的socket,同时在内核中创建一个发送缓冲区和接收缓冲区;调用write(),数据先写入发送缓冲区,如果发送字节太长,会被拆分成多个数据包发出,如果发送字节太短,就会现在缓冲区等待,等到足够多的字节积累成一定长度或等其他合适的时机发送出去;
调用read从接收缓冲区拿数据;
全双工:对于一个连接,既可以读数据,又可以写数据
如何避免粘包问题?
对于定长的数据包,保证每次都按固定大小读取即可,sizeof(Request)
对于变长的包,可以在包头位置约定一个包总长度的字段,从而知道了包结束位置;还可以在包和包之间使用明确的分隔符;
对于UDP协议,是一个一个把数据交付给应用层,有明确的数据边界,应用层要么收到完整的报文要么不收,不会出现半个的情况;
9.TCP异常情况
进程终止/机器重启:进程终止会释放文件描述符,仍然可以发送FIN,和正常关闭没有什么区别
机器掉电/网线断开:接收端认为连接还在,一旦接收端有写入操作发现连接已经不在了,就会进行reset;即使没有写入操作,TCP内置的保活定时器会定期询问对方是否还在,如果不在也会把连接释放