UDP
udp因为无连接,数据报格式发送,不保证可靠性,因此设计的很简单,我们来看看UDP是如何发送上层下来的数据报的。
UDP协议格式
首先我们看看UDP协议如何封装的。封装的也比较精简,因为传输层在网络层之上,此时已经可以通过网络层从这个主机发往另一个主机了,关键需要知道具体主机的哪个进程,因此需要记录端口号。同时记录UDP长度,校验和是用来最后进行数据校验的,如果有错误就会直接将数据报丢弃
16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度;
UDP特点
大家可以就把UDP当做寄信来看待。那么首先就是可以一次性发送完数据,然后就是寄信是不可靠的,可能你寄几次,中途有的先到有的后到,然后就是可能寄信途中信丢了。如果邮箱满了,就满掉出来了。所以就是无连接、不可靠、面向数据报。
面向数据报就是不会对从应用层传下来的数据做分割,而是一次性写到"信"里面,然后发送出去
UDP缓冲区
UDP没有真正意义上的发送缓冲区,因为写入高发送缓冲区的数据进步就是立即发送到网络里面。
UDP是有接收缓冲区的,但是不保证顺序,满了就丢弃新的
UDP注意事项
我们注意到,UDP协议的整个长度是16位,这就导致最大发送长度是64KB,超过这个长度的数据只能用户自己在应用层进行分包。
基于UDP的应用层协议
NFS:网络文件系统
TFTP:简单文件传输协议
DHCP:动态主机配置协议
BOOTP:启动协议(用于无盘设备启动)
DNS:域名解析协议
TCP
TCP协议格式

端口号
不多说了
序号和确认序号
是确认数据是否接收正确的机制,这个我们在下面详说。
4位首部长度
这个是记录除了数据以外,头部的长度。它是记录头部有多少层(一层432位)。我们可以看上面的图片,除了选项,上面5层是固定长度都会有的。而4位最多表示15层,因此返回就是4*[5,15]即[20,60]的范围。
6位标志
URG:紧急指针是否有效
ACK:确认号是否有效
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
16位窗口大小
这个也后面再说
16位校验和
对整个报文的校验,如果不通过这说明数据有问题,就要进行相关的操作
16位紧急指针
标识哪部分的数据紧急
选项
这个暂时也不看
确认应答机制(ACK)
在TCP里面,对发送的报文,对方都必须要给予应答,应答使用ACK的标志。

TCP对发送缓冲区的每个字节都进行了编号,即为序列号。

每个ACK应答都带有确认序列号,意思是告诉发送者,我已经接收到了哪些数据,下一次从哪里开始
我们说详细点,如果ACK的确认序列号是N,则说明N之前的数据都已经接收到了。TCP的每个报文是可以连续发送连续接收的,如果有几个ACK报文丢失了,但是最新的应答没有丢失,且回复的是都收到了,那么之前的ACK报文也可以不管。
如果连续发送了1-1000 1001-2000 2001-3000 ,最后只有1-1000和2001-3000的收到,最后也只会回复1001
捎带应答
为什么有序列号和确认序列号呢?这里是为了在回复的同时可以发送自己的数据报。例如别人再电话里面回答,她说听到了,也可以加上自己的问题。这就叫捎带应答
超时重传机制

A发送给B后,可能因为一些网络原因导致没有发送到B,如果A在一个时间段内没有收到对应的答复就会重新发送这个包
但这里有多种情况,也可能是B收到了,但是在发送ACK的时候,丢包了
此时B会收到很多重复的包,比如上图,收了两次同样的1-1000数据包。此时32位序号就起了去重作用。
重传时间
那么这个超时重传的时间具体是多少?应该找到一个不大不小的时间,如果太长可能导致重传效率太低,如果太短,可能会频繁发送报文。
为了保证最大效果的通信,会动态计算这个值。
连接管理机制
正常情况下,TCP要经过三次握手建立连接和四次挥手机断开连接
三次握手
我们和socket编程结合起来,首先服务器通过socket函数创建监听套接字,通过bind绑定ip和端口,然后启动监听,此时会在内核维护连接队列,并通过accept将已经连接的拿回。客户端通过socket函数直接创建接收套接字,通过connect自动被操作系统分配端口,然后与处于监听状态的服务器做连接。此时先是客户端发送SYN的连接请求,然后服务端正常收到报文后返回ACK+SYN的捎带应答,客户端收到后返回ACK,此时客户端已经知道对方收到连接了,因此这边可以确定建立连接。服务端也需要收到报文ACK才能真正确定建立的连接,否则会超时重传,如果此时还收到对应客户端的报文数据,会直接返回RST的重新建立连接的报文。
这里要再次说一下,listen函数底层会创建一个半链接的队列和连接队列 ,半连接队列是给只收到连接消息但没收到回复的连接管理的,accept是从连接队列里面拿连接的。
那么就有两种情况,服务端没有收到第三次握手的应答,那么就会重发SYN+ACK,并且会对客户端发送的后续报文返回RST ,这两种报文最终到达客户端的顺序是不确定的,如果是SYN+ACK先到,那么客户端就会重新发送SYN+ACK的报文,如果是RST的先到,客户端就会关闭,connect会返回-1,错误码就是连接被重置。如果客户端多次超时重传(Linux下是5次)都没收到报文,就会将这个半连接从半连接队列里面删除。因此根本没有被accept处理过。
四次挥手
如果是客户端主动关闭连接:首先会给服务端发送FIN的终止报文,服务器收到后立马回复,但此时不会进行捎带应答,因为可能此时可能服务器还没有处理好对客户端的数据,等数据处理好后,才会发送给客户端FIN的报文。客户端第一次发送FIN的时长和TCP的重传机制有关,第二次是tcp_fin_timeout的时间,一般是60s,第三次是两倍的2*MSL的时间,MSL是报文在网络里面传递的最长时间,一般MSL=60,如果过去2*MSL还没有收到服务器的重传,就会直接断开。
如果是服务端先关闭连接,其实就是对称的四次挥手,这就不讲了。
正确close
我们可以看到,第三次挥手是需要用户层主动调用close才能释放的,而我们要判断对方关闭连接是用读函数返回值为0判断的,但此时是CLOSE_WAIT的状态,需要用户调用close才行。如果不主动调用close,那么就会导致fd一直被占用不被释放,最终可能导致文件描述符满,不能接收新的连接。
这算是一种资源的泄露,因此关闭close是很重要的事情
服务器关闭立即重启的问题
如果服务器关闭立即重启,可能上次关闭前的报文还在网络中传输,此时系统会把这个端口关闭2*MSL的时间,自动清理网络中的旧报文,是2*MSL的原因是服务端最后关闭前可能发送了报文,进过来回的传递最大是2*MSL。可以使用setsockopt函数将其复用端口开启
滑动窗口
如果是发一个报文回复一个报文,那么最后就会导致效率变得很慢,因此有了连续发连续收的机制

上图效率慢

真正使用如上操作
窗口大小指的是无需等待回复的最大发送数据值,图中是4000,发送这四个段是,无需等待ACK可以直接发送。收到第一个ACK后,窗口会向后滑动,然后继续发送新的报文
而这一系列操作都是在发送缓冲区里面完成的。窗口越大,网络的吞吐率就越高
这种多包发送的情况,也会有一些问题,就是如何重传。
情况一:
数据报递达,ACK丢了。这种情况不必多担心,只要越靠后的ACK没丢,那么就越没事,因为应答序列返回的是下次发送的地点,如果后面报文收到了,说明下次应答序列可以从最新的开始。但是如果是偏前面的报文没丢,后面的丢了,那么就得从前面的开始重传。例如1-1000 1001-2000 2001-3000的,如果最后只收到1001,后面两个都丢了那么就只能从1001开始。如果前面两个丢了,后面3001没丢就从3001开始。
情况二:
数据报直接丢了

如果此时发送的某个包丢了,那么后面的报文都会发送这个丢弃报文起始的应答序列。让A主机从这里重传。例如上图要返回1001,那么如果收到的返回1001的应答小于三次就会超时重传
快重传
如果连续三次收到了同样的会马上进行重传,这叫快重传
流量控制
接收端的接收缓冲区是有限的,如果不加限制的发送,可能就会导致接收缓冲满,此时就会丢掉后面来的包,发送端收不到回复就会重传
接收端将自己能够接收的数据大小放入"窗口大小"的字段,通过ACK发送出去,窗口大小的字段值越大,说明能接收的报文越大,那么就会发更多的报文,如果越小,此时就会减缓报文发送的速度。
如果接收端的窗口大小为零了,此时发送端就不会再发送数据了,但是会是不是发送一个窗口探测包,返回报文告诉发送端实时的窗口大小。
接收端通过16位的窗口大小告诉对端。难道最大的接收缓冲区就只有2^16=65535字节吗?答案并不是,TCP首部还包含一个窗口扩大因子M,实际的窗口大小是窗口大小左移M位。
拥塞控制
如果每次盲目开始就往网络里面发送大量的数据报,可能此时网络拥堵导致堵上加堵。因此TCP采用慢启动的方式,从一次发一个报文开始,按指数形式往上增加
虽然是慢启动,但是增长的速度特别快。当然不能一直这么成倍的增长,会有一个阈值,超过这个阈值就是线性的增长。
ssthresh的初始值是16,之后就是网络拥塞是报数的一半。超过ssthresh就会线性增长,最后在一个阈值停下,这个阈值= min(网络拥塞,对方接收能力)
大家一定要和滑动窗口的长度结合起来,结合到一起。
因此滑动窗口实际的大小是由拥塞控制决定,拥塞控制又受到网络发送能力和对方接受能力的影响。
延迟应答
如果接收数据后立即返回,此时的窗口可能比较小,假设此时窗口剩余500K,总窗口1M,如果上层处理数据很快,10ms内拿了所有数据,那么我们就可以返回1M的窗口剩余。
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高.我们的目标是在保证网络不拥塞的情况下尽量提高传输效率;
面向字节流
如果发送的字节太长,会拆分成多个报文发送,如果字节太短,可能会等待,等超时了或者长度够了再发送。因此接收端获取的数据并不是一个完整的,可能一下发来了很多个,但是一个报文都没组成。所以TCP不像UDP必须一个sendto对应一个recvfrom,而是可以一个send对应多个recv或者多个send对应一个recv
黏包问题
因为面向字节流的原因,就会产生黏包问题。比如读取的时候刚好有一个报文流加半个其他报文流,那么我们需要自己在上层将两个报文分开。那么我们需要明确两个报文的边界,最简单的做法就是发送报文的时候,还将报文的大小用一个固定字节存储起来,这样就会先解析读取当前报文的长度,那么我们就可以分包了。
TCP异常情况
进程终止:进程终止会释放文件描述符,即发送FIN,和正常关闭没区别
机器重启:和进程终止相同
机器掉电/断网:接收端认为连接还在,一旦接收端有写入操作,接收端发现连接已经不在了,就会进行reset.即使没有写入操作,TCP自己也内置了一个保活定时器,会定期询问对方是否还在.如果对方不在,也会把连接释放.
TCP小结
可靠性:
校验和
序列号(按序到达)
确认应答
超时重发
连接管理
流量控制
拥塞控制
提高性能
滑动窗口
快速重传
延迟应答
梢带应答
其他
定时器(超时重传定时器,保活定时器,TIME_WAIT定时器等)
基于TCP协议
HTTP HTTPS SSH Telnet 等
TCP和UDP对比
TCP用于可靠传输的情况,应用于文件传输,重要状态更新等场景;
UDP用于对高速传输和实时性要求较高的通信领域,例如,早期的QQ,视频传输等。另外UDP可以用于广播;
UDP实现可靠传输
在应用层实现TCP的序列、应答序列、超时重传、确认应答、连接管理等,根据不同的场景来实现滑动窗口、延迟应答、快速重传、捎带应答等操作。这种应用层的实现可以直接决定实现哪些,去掉哪些模块来动态分配。

