UDP和TCP的报文详解

  • Post author:
  • Post category:其他




TCP和UDP报文详解



一、先说说端口号

TCP和UDP都是位于传输层的协议,传输层负责将数据从发送端传输到接收端。

端口号(Port)标识了一台主机上正在进行通信的一个具体的应用程序,在TCP/IP协议中,使用

源IP、源端口号、目的IP、目的端口号、协议号

的五元组来标识一个通信,即我们只要标注出这五个数据,就能唯一确定一条通信线路。

端口号的范围划分是:

  • 0~1023:知名端口号,其中HTTP、FTP、SSH等这些广泛使用的应用层协议,端口号都固定在前1024个端口号之中。
  • 1024~65535:操作系统动态分配的端口号,客户端程序的端口号,会由操作系统在这个范围内进行分配。

其中知名端口号是为了方便使用,给某些服务器固定分配的端口号:

  • SSH服务器:使用22端口
  • FTP服务器:使用21端口
  • Telnet服务器:使用23端口
  • HTTP服务器:使用80端口
  • HTTPS服务器:使用443端口

因此用户在写自己的程序使用端口号的时候,应该避免这些知名端口号。


一个进程可以和多个端口号绑定,即这个进程可以通过这些端口号分别建立连接,实现通信,但是一个端口号只能绑定一个进程,假如绑定多个进程,这个端口号在通信时无法判断应该将数据具体交给哪个进程!



二、Linux下查看网络状态的命令netstat

netstat是一个用来查看网络状态的重要工具,如果在Linux服务器的命令行上输入netstat显示没有该命令,则需要通过yum进行安装。


netstat [-nlptua]

命令行参数解释:

  • n:拒绝显示别名,能以数字显示的全部转化为数字
  • l:仅列出在Listen(监听)的服务状态
  • p:显示建立相关链接的程序名
  • t:仅显示TCP相关的选项
  • u:仅显示UDP相关的选项
  • a:(all)显示所有选项,默认不显示Listen相关

image-20220322135358003



三、UDP协议



UDP协议格式:

前两行是UDP的首部,也可以叫做UDP的报头,报头总共有8个字节。

UDP的报头是定长报头

,固定为8个字节,因此只要取出前八个字节即可实现报头与有效载荷的分离。

第一行的前16个比特位表示UDP数据报的源端口号,即数据报是从哪里发出的,后16位则表示的是目的端口号,即数据报需要向哪里发送。在内核中是使用哈希的方式通过端口号找到相对应的目标进程。

其中第二行的前16比特位代表UDP长度,表示的是

整个数据报(UDP首部加上UDP数据)的最大长度

,通过这个UDP长度,我们可以计算出该UDP报文的结尾在哪里。

第二行的后16比特位代表UDP的检验和,即对整个UDP数据报进行检验,在UDP协议下,如果检验和出错,该数据报会直接被丢弃。



UDP的特点:
  1. 无连接:知道对端的IP和端口号就可以直接进行传输,并不需要像TCP那样经过三次握手建立通信连接,结束通信时再经过四次挥手断开连接。
  2. 不可靠:没有确认机制,也没有重传机制;如果因为网络原因无法发送到对方,UDP协议层也不会给应用层返回任何的错误信息。
  3. 面向数据报:一旦发送,就必须将整个数据报的全部内容完全发送出去,同时读端在读取数据时,要么读整个数据报,要么就不读,不能只读一半。因此UDP不能灵活控制读写数据的次数和数量。

我们可以用寄信的例子来理解UDP的传输过程:每封信都有严格的界限(被信封包好),同时在写信或读信的时候都是以整封信为单位,并不能只写半封信或者只读半封信。而且在寄信过程中,信件有可能丢失,但是信件丢失后双方都不知道这件事情。



面向数据报:

应用层给UDP交付多长的报文,UDP就会原样发送,既不会对报文进行拆分,也不会将它们合并,假如发送端调用一次sendto方法,发送了100个字节,那么接收端也必须对应只能调用一次recvfrom方法,并一次性接收100字节,而绝不能循环调用10次recvfrom方法,每次接收10字节。



UDP的缓冲区:

UDP其实并没有真正意义上的

发送缓冲区

,调用了sendto之后会直接交给内核,由内核将数据传给网络层协议后进行后续的传输工作。

但是UDP是具有接收缓冲区的,只是这个接收缓冲区并不能保证收到的UDP数据报的顺序与发送端发送的顺序一致,因为在网络的发送的过程中,路由选择会影响数据的传输时间;同时这个缓冲区一旦满了,溢出的UDP数据报会被直接丢弃,并且根据UDP的机制,发送端不会再重新发送这些已经溢出的数据。


UDP的socket既可以读也可以写,是全双工的socket



UDP的注意事项:

UDP协议中UDP首部有一个16位的UDP长度,也就是说一个UDP数据报能传输的数据最大长度是

64KB

(包含了UDP首部),64KB在现在的网络环境下是一个非常小的容量,所以如果使用UDP要传输超过64KB的数据,就需要用户在应用层手动将数据分包,使用多个UDP数据报发送,并在接收端再手动将它们拼接起来。



TCP协议

TCP的全称是:

传输控制协议(Transmission Control Protocol)



TCP的协议格式:

首先需要说明的是4位报头长度,它表示该TCP头部有多少个32位bit位(即有多少个4字节),因此TCP头部的最大长度应该是(2^4-1) * 4 = 15 * 4 = 60个字节,又由示意图可以知道报头占据了20个字节,因此选项字段最多可以有40个字节。选项字段和TCP报头共同组成了TCP的头部,在读取数据时应该先读取20字节,取出4位报头长,如果4位报头长度的值为5,那么说明TCP首部长度只有4*5 = 20字节,刚刚读取的就是所有的头部内容,下面接着的就是数据内容;如果报头长度的值大于5,则后面还要读取一部分选项字段。

通过对比我们发现,TCP数据包格式中并没有像UDP数据报中的16位UDP长度字段来标识数据的整体大小,这与两者的传输方式有关,UDP是面向数据报传输,每次要传输一个完整的数据报,所以必须标出每个数据报的具体大小,但是TCP是面向字节流传输的,可以一个字节一个字节传输,因此TCP可以一个字节一个字节放在缓冲区就行了,应用层想读多少根据应用层来自行决定。

通过TCP的16位检验和,可以判断数据完整性,因此TCP不需要在数据格式中添加具体的数据长度了。

因为TCP是安全的传输协议,因此它采用了

确认应答机制

,这种机制的可靠性体现在:

只要收到了对应的应答,就可以认为,之前的数据对方已经收到了!

现在的ACK其实是可以携带数据的,但是我们在这里方便理解,就先认为它并不携带数据,只是对收到数据做出应答。所以在计算机通信中并不存在绝对可靠,

因为ACK只能保证ACK前的数据收到了,但是最新的一条通信数据永远无法被ACK

。如上图中的ACK2,就没有人可以保证它的可靠性啦。

32位序号:因为TCP是可靠传输,所以

发送的信息顺序也要保证接收方接受的顺序一致

,但是如果有多条数据,在发送过程中因为路由选择不同,一定是会有先发出的后到达,后发出的先到达的现象。所以就需要给数据进行编号,这样接收方根据编号再将数据进行排列,这样就可以保证数据报文的顺序了,32位序号应该有发送方在发送数据报之前填写完毕。

32位确认序号:我们已经知道了TCP是确认应答机制,所以如果发送方发送了三份报文,分别编号5,6,7。接收方也会返回三条ACK确认信息,我们怎么知道这三条ACK分别确认的是哪条报文呢?这里就要用到确认序号,但是这里有一个重要的点:

ACK所携带的确认序号是报文序号 + 1

,即确认5号报文的确认序号是6;确认6号报文的确认序号是7;确认7号报文的确认序号是8。原因是确认序号告知的是发送方,前面的报文我已经收到了,下一份报文应该从确认序号开始发送!如果在接收方发送ACK过程中,7,8两个ACK丢失,发送方只收到了9号ACK,发送方还会去发送6,7两份报文吗?不会!9号ACK代表这前面所有的报文接收方都收到了,发送方只需要继续从9号报文开始发送就行了!所以确认序号的存在,允许TCP中出现少量的丢包现象。假如发送方给接收方发送了0到10000号报文,但是只收到了确认序号为5000的ACK,发送方会认为:0~4999号报文接收方一定收到了,但是后面的报文无法保证,所以会从5000开始重新发送。



这里还会有一个高频的面试题:为什么TCP要成对出现序号和确认序号?只有序号不就行了吗?发送方在这里填写序号,接收方在这里填写确认序号即可。

原因是

TCP也是全双工的通信协议

,双方在建立通信后,都可以发送消息也都可以接收消息,当作为发送方时,自己要编写序号,同时要留意对方给自己返还的确认序号;但当自己作为接收方时,也要按对方填写的序号将收到的信息排序,并将自己确认收到的信息序号加1作为确认序号返还给对方。

16位窗口大小:

发送方的接收缓冲区剩余空间的大小

,通过这个可以实现流量控制。虽然TCP不像UDP那样,缓冲区溢出后,会导致丢包,因为TCP会有重传机制,但是传输过去的信息已经耗费了网络资源,再次传输会继续消耗网络资源,这对于网络通信是非常浪费的行为。因此我们设置了窗口大小,发送方可以在报文中告知对方,我的接收缓冲区剩余空间还有多少,这样等到接收方给发送方发送数据时,可以防止对方发送数据过多;而接收方在接收到消息后返还的ACK报文中也可以带上自己接受缓冲区剩余空间的大小,方便发送方控制自己的发送数据量。

TCP协议下,接收端会接收到大量形形色色的报文,怎么该辨别每个报文分别是干什么的呢?因此就需要六个标志位来帮助接收端识别自己接收的报文的身份。比如ACK报文的ACK标志位会被设置为1,当接收端发现之后,就知道这份报文是作为ACK确认发送过来的,就需要着重注意一下它的确认序号。

六个标志位:

  • URG:紧急指针是否有效
  • ACK:确认号是否有效
  • PSH:提示接收端应用程序立刻从TCP缓冲区将数据读走
  • RST:对方要求重新建立连接;我们将携带RST标识的报文称为

    复位报文段
  • SYN:请求建立连接;将携带SYN标识的报文称为

    同步报文段
  • FIN:通知对方,本端即将关闭,将携带了FIN标识的报文称为

    结束报文段

这里SYN和ACK以及FIN我们应该都较为熟悉,重点介绍一下PSH、RST和URG三个标志位

PSH(Push):是在提示接收端,接收端的接收缓冲区快要满了。因为经过上面的窗口大小我们知道,接收端返回的ACK中也会标上自己缓冲区的大小,所以发送端是可以知道接收端的剩余缓冲区还有多少,当发送端发现接收端的接收缓冲区大小快要满了,就会在报文上携带PSH标志位,来提醒接收端的应用层程序立即将接收缓冲区中的数据读取完毕,这样不会延误自己继续发送数据的效率。

RST(Reset):即要求重新建立连接,我们举个例子来对它进行解释:

上图中①,②,③表示我们TCP建立连接的三次握手,其中①,②两份数据报丢失我们都不担心,因为此时并没有建立起来连接,所以只需要再次发送SYN重新建立连接就行了,但是如果③号报文,即客户端给服务器的ACK丢包,那问题就严重了。我们上文谈过ACK的确认应答机制并非绝对安全,永远有一条最新的数据是无法被确认的,③号的ACK就是这条数据。当客户端发出③号ACK后,就认为TCP连接已经建立,开始准备发送数据,但是如果发生了极小概率时间,该报文丢失了,那么就会产生一个信息差:

客户端认为TCP连接已经建立,但是服务器没有收到客户端的ACK,认为三次握手失败,TCP连接建立失败!

此时客户端给服务器发送数据,服务器发现TCP并没有建立,对端还在给自己发送数据,就会意识到,在TCP连接建立过程中出现了问题,此时服务器就会给客户端发送一个报文,其中携带着RST标志位,告诉客户端,你要重新建立TCP连接!当客户端收到这份报文,就意识到TCP并没有建立成功,此时它就会停止发送数据,继续上面的三次握手,重新建立TCP连接了。同理,如果TCP传输过程中,客户端突然断开连接,并没有进行四次挥手,服务器还认为TCP连接存在,给客户端发了数据,客户端收到后,也会发送一个携带RST标志位的报文返给服务器,但是不同的是服务器看到这份报文后,就会断开连接了,因为

建立连接永远是客户端向服务器请求,服务器是不会主动向客户端请求连接的

URG(urgent):

(不重要,已经很少使用)

如果我们发送的某些信息,发出后就后悔了,不想让对方收到这些信息,所以需要发出通知让对方不要接收刚才的消息,但是因为TCP的有序性,对方收到不要接收信息的通知一定是晚于接收信息到达对方那里的,所以需要一种

打破按序到达的机制

。打个比方,我们在医院的挂号处排队,先来的排在前面,但是这时来了一位急诊病人,我们就会让他先去挂号。URG标志位被设置后,说明这份报文中有些数据是需要被优先处理的。即16位紧急指针。16位紧急指针是个偏移量,将起始地址加上偏移量后,就可以指向紧急数据。我们可以找到紧急数据的起始地址,但是它并没有标识结束地址的信息,因此每次的紧急数据只能发送

一个字节

16位检验和:对整个TCP报文进行检验,如果发生错误,直接丢弃该报文,但是TCP有重传机制,我们并不担心报文错误的问题,假如1,2,3,4,5报文中3号报文发生了错误,被TCP丢弃掉了,此时只需要将ACK3传给发送方即可,因为发送方会从ACK携带的序号开始,把后面的报文再发一遍。

至此需要介绍的内容结束了,以上为笔者自己整理的学习笔记,可能其中有部分内容不太正确,也不太完整,还请大家指正



版权声明:本文为weixin_45523353原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。