深入理解UDP/TCP协议

在之前的网络编程的博文里,我们简单了解了下UDP协议和TCP协议,这次我们主要对这两个协议进行深入的了解(其实是对TCP协议进行升入了解)

https://blog.csdn.net/LSFAN0213/article/details/81537507

1.再来看看端口号

端口号标识了一个主机上进行通信的不同应用程序;

在TCP/IP协议中,用“源IP”,“源端口号”,“目的IP”,“目的端口号”,“协议号”这样一个五元组来标识一个通信

前面我们说过有一些知名协议的端口号是固定的,我们在写代码时,只能使用1024以后的端口号,那我们就来看看端口号的划分

0—1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他们的端口号都是固定的

1024—65535:操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围分配的

认识一些知名的端口号:

ssh服务器 22端口

ftp服务器 21端口

telnet服务器 23端口

http服务器 80端口

https服务器 443端口

下来,进入主题,正式了解UDP、TCP协议

UDP协议端格式:

16位UDP长度表示整个数据报的最大长度

如果校验和出错,那么整个UDP数据报会被丢弃(不可靠传输,一旦数据有丢失,就直接将数据报丢弃)

在前面,我们也说过UDP的特点,我们再回顾一下

UDP传输过程类似于寄信

无连接:知道对端的IP和端口号就可以直接进行传输,不需要建立连接;

不可靠:没有确认机制,没有重传机制,如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息

面向数据报:不能够灵活的控制读写数据的次数和数量;(应用层交给UDP多长的报文,UDP会原样发送,既不会拆分,也不会合并)

比如:如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节,而不能循环调用10次recvfrom,每次接收10个字节

UDP的缓冲区

UDP没有真正的发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输操作

UDP具有接收缓冲区,但是这个缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致,如果缓冲区满了,再到达的UDP数据就会被丢弃

我们在上面的图中可以看到,UDO协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是2的16次方(65535)也就是64k,然而在当今的互联网环境下,64k是一个非常小的数字

如果我们发送的数据超过了64k,就需要在应用层手动分包,多次发送,并在接收端手动拼装

TCP协议

TCP协议段格式

对比UDP协议段格式,TCP就要复杂很多,因为TCP全称为 传输控制协议,它要对数据的传输做一个详细的控制

其中4位的TCP报头长度:表示TCP头部有多少个32位bit(有多少4字节);所以TCP头部最大长度为15*4=60

6位标志位:

URG:紧急指针是否有效

ACK:确认信号是否有效

PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走

RST:对方要求重新建立连接;我们把携带RST标识称为复位报文段

SYN:请求建立连接;我们携带SYN标识的称为同步报文段

FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段

TCP要经过三次握手建立连接,四次挥手断开连接

这里的具体内容见:https://blog.csdn.net/LSFAN0213/article/details/81747430

TCP是面向连接的可靠传输,那它可靠在哪里呢

确认应答机制

TCP将每一个字节的数据都进行了编号,即为序列号,每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据,下一次你从哪里开始发送

超时重传机制:

主机A发送数据给主机B之后,由于一些因素(比如网络堵塞)数据无法到达主机B,主机A在一个特定的时间内没有接收到主机B的确认应答,就会进行重发。

但是,也可能是因为主机B发过来的ACK丢失了,此时,主机A就会一直重复发送,主机B就会收到很多重复的数据,那么TCP协议就需要能识别出哪些包是重复的包,并且把重复的包丢弃掉,这个时候,我们可以利用前面所讲的序列号很容易做到去重的效果。

那这个超时的时间如何确定是多长?

最理想的情况下,应该是找到一个最小的时间,保证“确认应答一定能在这个时间段内返回”,但是,这个时间的长短,我们不好控制,随着网络环境的不同,是有差异的。如果这个时间设置的太长,会影响效率,如果时间太短,可能会引起频繁的发送重复的数据包。

所以,TCP为了保证无论在任何情况下都可以高效的进行通信,因此会动态的计算这个最大超时时间

Linux中(windows也是如此),超时以500ms为一个单位进行控制,每次判定超市重发的超时时间都是500ms的整数倍,如果发一次之后,仍然得不到应答,会等待2*500ms后进行重传。如果还是得不到应答,会等待4*500ms后进行重传,以此类推,以指数形式递增。

累计到一定的重传次数,TCP会认为网络或者对端主机出现异常,强制断开连接。

滑动窗口机制:

上面,我们说到了确认应答机制,对每一个发送的数据段,都会有一个ACK确认应答,收到ACK后在发送下一个数据段,这样做,性能就会很差,每发送一个数据,都要等待对方的ACK确认应答,在数据往返时间较长的情况下,显然这种方式是很不合理的。

既然,这样一发一收的方式性能很差,那么,我们就一次发送多条数据,就可以提高性能(其实就是将多个段的等待时间重叠在一起)

 

窗口的大小指的是无需等待确认应答而可以继续发送数据的最大值,上图的窗口大小就是4000个字节(四个段)

发送前四个段的时候,不需要等待任何的ACK,直接发送

收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据,依次类推

操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答,只有确认应答过的数据,才能从缓冲区删掉

窗口越大,网络吞吐率就越高。

那么,如果在这期间,出现了丢包,应该如何进行重传?在这里,应该分两种情况讨论:

一、数据报已到达,ACK丢了

这种情况,部分ACK丢了没关系,因为可以通过后序的ACK进行确认

情况二:数据包直接丢失

当一段报文丢失后,发送端会一直收到1001这样的ACK,就像是在提醒发送端“我想要的是1001”一样

如果发送端主机连续三次收到了1001这样的ACK,就会将对应的数据1001---2000重新进行发送

这个时候接收端收到了1001之后,再次返回的ACK就是7001了,接收端其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中

这种高速重发机制也叫作“快重传”

流量控制:

因为接收端处理数据的速度是有限的,如果发送端发送的速度太快,导致接收端的缓冲区被塞满,这个时候如果发送端继续发送数据,就会造成丢包,继而出现丢包重传等一些列反应。所以TCP就会根据接收端处理数据的能力,来决定发送端的发送速度,这个机制就叫做“流量控制”机制

接收端将自己可以接收的缓冲区大小放入TCP首部的“窗口大小”字段,通过ACK端通知发送端,如果窗口大小字段越大,说明网络的吞吐率越高。接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端,发送端接收到这个通知的时候,就会降低自己的发送速度,如果接收端的缓冲区满了,就会将窗口设置成0,这个时候发送端不再发送数据,但是要定期发送一个窗口探测数据段,使接收端把窗口的大小发送给发送端。

拥塞控制:

在不清楚网络环境的情况下,如果一开始就发送大量的数据,这也是有问题的,所以,TCP引入慢启动机制,先发少量的数据探探路,摸清楚网络环境之后再决定按照多大的速度发送

这个地方我们引入一个拥塞窗口,开始发送的时候,拥塞窗口为1,每次收到一个ACK应答,拥塞窗口+1,每次发送数据包的时候,将拥塞窗口和接收端主机反馈窗口大小做比较,取较小的值作为实际发送的窗口。但是这样的增长速度过快我们不能让增长变得很快,为了不增长的那么快,因此不能使拥塞窗口单纯的加倍,此处引入一个慢启动的阈值,当拥塞窗口超过这个阈值的时候,不再按照指数的方式增长,而是按照线性的方式增长。当TCP开始启动的时候,慢启动阈值等于窗口最大值,每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1。

拥塞控制,其实就是TCP协议想尽可能快的把数据传输给对方,但是又要避免网络造成太大压力的折中方案。

延时应答:

如果接受数据的主机立刻返回ACK应答,此时返回的窗口会比较小

假设接受端数据缓冲区为1M,一次收到了500K的数据,如果立刻应答,返回的窗口就是500K。但实际上可能处理端处理的速度比较快,10ms之内就把500K数据从缓冲区消费掉了。在这种情况下,接收端处理还没有达到自己的极限,即使窗口再放大一些,也能处理过来

如果接受端稍微等一会再应答,比如等待200ms再应答,这个时候返回的窗口大小就是1M。窗口越大,网络吞吐率就越高,传输效率就越高,我们的目标就是在保证网络不拥塞的情况下尽量提高传输效率

捎带应答

在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是“一发一收”的,意味着客户端给服务器说了“How are you”,服务器也会给客户端回复“Fine,thank you”,那么这个时候ACK就可以搭顺风车,和服务器回应的“Fine,thank you”一起回给客户端

面向字节流:

创建一个TCP的socket,同时在内核中创建一个发送缓冲区和接收缓冲区

调用write的时候,数据会先写入发送缓冲区,如果发送的字节数太长,或被拆分成多个TCP的数据包发送

如果发送的数据包太短,就会先在缓冲区里的等待,等待缓冲区长度差不多的时候,或者其他合适的时机发送出去

接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区

然后应用程序可以调用read从接收缓冲区里拿数据

另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于这么一个连接,既可以读数据,也可以写数据,这个概念叫做全双工。

 

 

 

展开阅读全文

没有更多推荐了,返回首页