应用层中调用write/send``recv/read
这些系统接口,并没有将数据发送到网络中,而是向下交付到传输层协议,具体什么时候发送数据,由传输层根据一些策略进行数据的实际发送。
传输层的主要功能就是负责 数据的传输,包括如果数据丢失,重复,乱序,发送太快太慢等等一些数据传输可靠性问题。传输层就是根据用户的要求从而解决数据传输的问题。传输层的协议主要由TCP和UDP两种协议。TCP协议是可靠传输的,如果用户想要更可靠的传输就用TCP协议;如果用户追求效率,那么可以使用UDP协议,它是不可靠的,没有面向连接的,因此速度会比TCP协议快一些。
一.UDP
1.1 UDP协议格式
由于UDP报头是固定8字节的,所以根据16位UDP长度与8字节,可以知道当前UDP报文中有效载荷的大小,因此不会出现有效载荷没有读完的情况。当把有效载荷读完后,UDP协议根据报头中的16位目的端口号,将有效载荷中的数据交付给上层应用程序。
由于传输层属于操作系统层,而UDP在内核中是一个结构体,在底层传输不需要序列化与反序列化,因为在所有操作系统中,都强制规定了协议结构体的报头大小位8字节,不用考虑内存对齐了。
1.2 特点
- 无连接:知道对端的端口号即可通信,不用考虑对方在不在。
- 不可靠:没有确认机制,没有重传机制
- 面向数据报:应用层交给UDP多长的报文,UDP原样发送和接收。这种方式不够灵活,但简单。
1.3 UDP缓冲区
- UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议后,进行后续传输。
- UDP具有接收缓冲区,但这个缓冲区不能保证收到的UDP报的顺序和发送的一致,如果缓冲区满了,再到达的UDP数据就会被丢弃。
- 使用UDP发送数据,一次最多只能发送64KB,超过该容量,用户只能自己分片。
二.TCP
2.1 TCP协议格式
- 4位首部长度:以4字节位单位,标识TCP报头的大小在20-60字节之间。根据这个字段,可以将TCP有效载荷与报头分离
- 32位序号、32位确认序号:TCP会给报文一个序号,这个序号能保证在TCP接收报文时,报文不会重复、不会丢失、接收顺序不会乱。32位确认序号就是对端告诉接收方,我接收到了确认序号之前的所有报文,你下次发送的报文序号应该从确认序号开始。
- 16位窗口大小:里面指明了自己接收缓冲区的剩余容量,防止对端发送消息过快/过慢,这种机制就是流量控制。
- URG:紧急指针标识位,标识该报文中有紧急数据
- ACK:标识该报文是一个应答报文
- PSH:催促对端快速处理缓冲区中的数据,让缓冲区的数据处于就绪状态
- RST:当客户端与服务器进行连接协商时,如果客户端认为连接建立成功,服务端连接失败。如果此时客户端发送消息,但是服务端因为没有连接,所以导致双方连接状态不一样,此时服务器就会给客户端发送一个RST报文,让客户端端重新建立连接
- SYN:标识该报文是在请求连接
- FIN:标识该报文是在请求断开连接
- 上面六位标志是为了区分不同种类的报文,对于每一种报文有其处理方式,并且多个标志可以同时设置。
- 紧急指针(带外数据):填入紧急数据(1字节)在缓冲区中的偏移量,可以通过
recv
第四个参数flags
设置为MSG__OOB
2.2 TCP可靠性
如果要理解TCP的可靠性,那么我们就要明白什么时候是不可靠的。比如丢包(大量,少量)、重复、乱序、发送太快/太慢,这些都是不可靠的表现。那么TCP是如何知道自己发送的报文丢了呢?这就要引入TCP的确认应答机制了,如果在一个时间段内没有收到应答报文,那么TCP就会认为自己的报文丢失了,然后重新发送报文。
TCP的报文按照顺序发送,但因为网络原因,它们有可能是乱序到达的,如果要保证可靠性,TCP就要给每个报文一个序号,这个序号既可以让报文有序接收,又可以防止报文重复的情况发生。
- 确认序号X:x-1之前的报文收到了,下次请从x开始发送。这样可以更细粒度的找到丢包原因,允许少量应答报文丢失
TCP可靠性机制:
- 确认应答(ACK)机制
- 超时重传
TCP为了保证无论在任何环境下都能比较高性能的通信,会动态计算等待的时间,Linux中,超时是以500ms为单位进行控制的,每次判定重传的时间都为500ms的整数倍。如果重发一次后,仍然得不到应答,等待2500ms后再进行重传。如果仍然得不到应答,等待4500ms,以此类推。如果在经过一定次数等待后,仍然收不到应答,那么TCP认为网络/对端主机出现异常,强制关闭连接。
- 连接管理机制
在双发主机通信时,双发TCP要经过三次握手建立连接,当通信结束时,双方TCP要经过四次挥手断开连接。
连接在操作系统就是一种数据结构,维护这个连接是要消耗系统资源的。
三次握手和四次挥手:
建立连接的过程是TCP自动完成的,即OS自动完成的,我们上层调用的connect()
是发起连接,然后阻塞等待连接完成获取连接,accpet()
是默认阻塞等待连接完成,获取连接。
三次握手是判断双方通信信道流通的最小成本。当三次握手中,前两个报文丢失,那么客户端会触发超时重传,如果第三个报文丢失,那么客户端与服务器对于连接的状态认知不一样,服务器会向客户端发送连接重置报文(RST)。三次握手实际上是包含4个报文,但是为了双方尽快建立连接,所以TCP将中间两个报文使用捎带应答的机制合并在一起。
四次挥手由断开连接的一方发送FIN报文,然后另一方发送ACK报文同意断开连接,但是有可能此时被断开连接的一方还要发送一些数据,等发完数据之后再给对端发送FIN报文请求断开连接。所以没有将FIN和ACK组合在一起。
- 状态变化:
- FIN_WAIT1:
- FIN_WAIT2:
- CLOSE_WAIT:一方收到FIN报文后会进入CLOSE_WAIT状态,这种状态如果不调用
close
关闭连接,那么会一直保持这种状态 - LASK_ACK:被动断开连接的一方当发送FIN报文后处于的状态
- TIME_WAIT:主动断开连接的一方等待2个MSL时间,确认等到报文被对方收到,防止下次建立连接时产生影响。在处于TIME_WAIT状态时,由于端口号被连接占用,所以导致再次启动程序时,就会报
band error
错误。但是对于服务器来说,这种情况是不允许发生的,我们可以设置服务器忽略TIME_WAIT状态,直接重启服务。- setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen)
- sockfd:目标套接字
- level:哪一层,通常填写SOL_SOCKET
- optname:属性名,SO_REUSEADDR|SO_REUSEPORT
- optval:填入整型变量的地址,这个变量存1
- socklen_t:optval的实际长度
- setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen)
- 流量控制
双方通信时,会将自己接收能力设置到窗口大小中,双方就可以根据对方接收的能力选择发送报文的速度。当自己的接收缓冲区满时,就会将窗口大小设置为0,这时发送方不在发送数据,而是定期发送探测窗口,询问对方接收能力。同时如果接收方的缓冲区有空间时,接收方也会向对方发送一个窗口更新通知
- 拥塞控制
在发送数据时,如果出现少量丢包问题,则会触发超时重传机制。但是如果出现大量丢包,就可能是网络出现故障(网络拥塞),此时TCP就会触发慢启动机制,先减少发送量,探明网络拥塞情况,在不会加重网络拥塞的情况下,逐步增加发送量,以让网络尽快恢复正常。
- 拥塞窗口:衡量网络健康状态的指标
网络的状态是一直变化的,所以拥塞窗口也是一直变化的。发送开始时候,拥塞窗口大小为1,每一次收到ACK,拥塞窗口按指数级别增长。每次发送数据时,滑动窗口大小取拥塞窗口和对端接收能力的较小值。当拥塞窗口的大小超过一个阈值(ssthresh)时,按照线性增长,当发生网络拥塞时,重新设置拥塞窗口大小,阈值变为上一次探测的拥塞窗口大小的一半。
- 校验和&序列号
TCP效率问题:
- 滑动窗口
双方主机在通信时,如果收报文和发报文的顺序是串行的,那么必然通信效率不高。此时TCP引入了滑动窗口,使得在一个窗口内的数据可以并行发送。
)
那么滑动窗口是什么呢?我们知道TCP的发送缓冲区是以字节为单位的,可以看作一个大数组,滑动窗口就是这个数组里的一个子数组,数组下标即为序号。滑动窗口将发送缓冲区划分为三部分:已经收到应答的数据,可以直接发送没有收到应答,不能发送的数据区域。上层将数据放到尚未发送的区域,TCP从可以发送区域中取数据,这种模式就是生产消费模式。
滑动窗口的大小取决于网络拥塞情况和对方的接收能力(16位窗口大小),它的大小是动态变化的。当收到了X序号的应答后,滑动窗口可以向右移动到X位置处。
- 快重传(高速重发控制)
当发送端连续收到三个相同的确认序号时,就说明该报文已经丢失,立即触发快重传机制。快重传机制和超时重传机制会同时使用,当不满足快重传触发条件时,就会触发超时重传机制。快重传决定数据传输效率上限,而超时重传决定数据传输效率下限。
- 延迟应答
当接收到一个报文后,接收方等一会儿,等上层用户把数据处理一部分,以告诉对方更大的窗口大小,那么对方的滑动窗口就有可能更大,发送的数据可能会更多,借此增加了通信效率。
- 不是所有包都延迟应答,一般每隔N个包就延迟应答一次或者超过最大延迟时间就应答一次。在不同系统中策略不同
- 捎带应答
在延迟应答的基础上, 我们发现, 很多情况下, 客户端服务器在应用层也是 "一发一收" 的. 意味着客户端给服务器说了 "How are you", 服务器也会给客户端回一个 "Fine, thank you";那么这个时候ACK就可以搭顺风车, 和服务器回应的 "Fine, thank you" 一起回给客户端 。
2.4 基于TCP协议的应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP