查看原文
其他

UDP,你要耗子喂汁呀!

程序人生 2020-12-18

The following article is from 程序员cxuan Author cxuan


作者 | cxuan
来源 | 程序员cxuan(ID:cxuangoodjob)
运输层位于应用层和网络层之间,是OSI分层体系中的第四层,同时也是网络体系结构的重要部分。运输层主要负责网络上的端到端通信。
运输层为运行在不同主机上的应用程序之间的通信起着至关重要的作用。下面我们就来一起探讨一下关于运输层的协议部分。

运输层概述

计算机网络的运输层非常类似于高速公路,高速公路负责把人或者物品从一端运送到另一端,而计算机网络的运输层则负责把报文从一端运输到另一端,这个端指的就是端系统。在计算机网络中,任意一个可以交换信息的介质都可以称为端系统,比如手机、网络媒体、电脑、运营商等。
在运输层运输报文的过程中,会遵守一定的协议规范,比如一次传输的数据限制、选择什么样的运输协议等。运输层实现了让两个互不相关的主机进行逻辑通信的功能,看起来像是让两个主机相连一样。
运输层协议是在端系统中实现的,而不是在路由器中实现的。路由只是做识别地址并转发的功能。这就比如快递员送快递一样,当然是要由地址的接受人也就是 xxx 号楼 xxx 单元 xxx 室的这个人来判断了!
数据包经过每层后,该层协议都会在数据包附上包首部,一个完整的包首部图如上所示。
在数据传输到运输层后,会为其附上TCP首部,首部包含着源端口号和目的端口号。
在发送端,运输层将从发送应用程序进程接收到的报文转化成运输层分组,分组在计算机网络中也称为 报文段(segment)。运输层一般会将报文段进行分割,分割成为较小的块,为每一块加上运输层首部并将其向目的地发送。
在发送过程中,可选的运输层协议(也就是交通工具) 主要有TCP和UDP,关于这两种运输协议的选择及其特性也是我们着重探讨的重点。


TCP 和 UDP 前置知识

在TCP/IP协议中能够实现传输层功能的,最具代表性的就是TCP和UDP。提起 TCP和UDP,就得先从这两个协议的定义说起。
TCP叫做传输控制协议(TCP,Transmission Control Protocol),通过名称可以大致知道TCP协议有控制传输的功能,主要体现在其可控,可控就表示着可靠,确实是这样的,TCP为应用层提供了一种可靠的、面向连接的服务,它能够将分组可靠的传输到服务端。
UDP叫做用户数据报协议(UDP,User Datagram Protocol),通过名称可以知道UDP把重点放在了数据报上,它为应用层提供了一种无需建立连接就可以直接发送数据报的方法。
怎么计算机网络中的术语对一个数据的描述这么多啊?
在计算机网络中,在不同层之间会有不同的描述。我们上面提到会将运输层的分组称为报文段,除此之外,还会将TCP中的分组也称为报文段,然而将 UDP的分组称为数据报,同时也将网络层的分组称为数据报。
但是为了统一,一般在计算机网络中我们统一称TCP和UDP的报文为 “报文段”。

套接字

在TCP或者UDP发送具体的报文信息前,需要先经过一扇 门,这个门就是套接字(socket),套接字向上连接着应用层,向下连接着网络层。在操作系统中,操作系统分别为应用和硬件提供了接口(Application Programming Interface)。而在计算机网络中,套接字同样是一种接口,它也是有接口API 的。
使用TCP或UDP通信时,会广泛用到套接字的API,使用这套API设置IP地址、端口号,实现数据的发送和接收。
现在我们知道了, Socket 和TCP/IP没有必然联系,Socket的出现只是方便了 TCP/IP的使用,如何方便使用呢?你可以直接使用下面Socket API的这些方法。

套接字类型

套接字的主要类型有三种,下面我们分别介绍一下
  • 数据报套接字(Datagram sockets):数据报套接字提供一种无连接的服务,而且并不能保证数据传输的可靠性。数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User DatagramProtocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
  • 流套接字(Stream sockets):流套接字用于提供面向连接、可靠的数据传输服务。能够保证数据的可靠性、顺序性。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议
  • 原始套接字(Raw sockets): 原始套接字允许直接发送和接收IP数据包,而无需任何特定于协议的传输层格式,原始套接字可以读写内核没有处理过的IP数据包。

套接字处理过程

在计算机网络中,要想实现通信,必须至少需要两个端系统,至少需要一对两个套接字才行。下面是套接字的通信过程。
  1. socket中的API用于创建通信链路中的端点,创建完成后,会返回描述该套接字的套接字描述符。
就像使用文件描述符来访问文件一样,套接字描述符用来访问套接字。
  1. 当应用程序具有套接字描述符后,它可以将唯一的名称绑定在套接字上,服务器必须绑定一个名称才能在网络中访问
  2. 在为服务端分配了socket并且将名称使用bind绑定到套接字上后,将会调用listen api。listen 表示客户端愿意等待连接的意愿,listen 必须在accept api之前调用。
  3. 客户端应用程序在流套接字(基于TCP)上调用connect发起与服务器的连接请求。
  4. 服务器应用程序使用acceptAPI接受客户端连接请求,服务器必须先成功调用bind和listen后,再调用accept api。
  5. 在流套接字之间建立连接后,客户端和服务器就可以发起read/write api调用了。
  6. 当服务器或客户端要停止操作时,就会调用 close API 释放套接字获取的所有系统资源。
虽然套接字API位于应用程序层和传输层之间的通信模型中,但是套接字API不属于通信模型。套接字API允许应用程序与传输层和网络层进行交互。
在往下继续聊之前,我们先播放一个小插曲,简单聊一聊IP。

聊聊 IP

IP是Internet Protocol(网际互连协议)的缩写,是TCP/IP体系中的网络层协议。设计IP的初衷主要想解决两类问题
  • 提高网络扩展性:实现大规模网络互联
  • 对应用层和链路层进行解藕,让二者独立发展。
IP是整个TCP/IP协议族的核心,也是构成互联网的基础。为了实现大规模网络的互通互联,IP更加注重适应性、简洁性和可操作性,并在可靠性做了一定的牺牲。IP不保证分组的交付时限和可靠性,所传送分组有可能出现丢失、重复、延迟或乱序等问题。
我们知道,TCP协议的下一层就是IP协议层,既然IP不可靠,那么如何保证数据能够准确无误地到达呢?
这就涉及到TCP传输机制的问题了,我们后面聊到TCP的时候再说。

端口号

在聊端口号前,先来聊一聊文件描述以及socket和端口号的关系
为了方便资源的使用,提高机器的性能、利用率和稳定性等等原因,我们的计算机都有一层软件叫做操作系统,它用于帮我们管理计算机可以使用的资源,当我们的程序要使用一个资源的时候,可以向操作系统申请,再由操作系统为我们的程序分配和管理资源。
通常当我们要访问一个内核设备或文件时,程序可以调用系统函数,系统就会为我们打开设备或文件,然后返回一个文件描述符fd(或称为ID,是一个整数),我们要访问该设备或文件,只能通过该文件描述符。可以认为该编号对应着打开的文件或设备。
而当我们的程序要使用网络时,要使用到对应的操作系统内核的操作和网卡设备,所以我们可以向操作系统申请,然后系统会为我们创建一个套接字 Socket,并返回这个Socket的ID,以后我们的程序要使用网络资源,只要向这个Socket的编号ID操作即可。而我们的每一个网络通信的进程至少对应着一个Socket。向Socket的ID中写数据,相当于向网络发送数据,向Socket中读数据,相当于接收数据。而且这些套接字都有唯一标识符——文件描述符fd。
端口号是16位的非负整数,它的范围是0-65535之间,这个范围会分为三种不同的端口号段,由Internet号码分配机构IANA进行分配
  • 周知/标准端口号,它的范围是0-1023
  • 注册端口号,范围是1024-49151
  • 私有端口号,范围是49152-6553
一台计算机上可以运行多个应用程序,当一个报文段到达主机后,应该传输给哪个应用程序呢?你怎么知道这个报文段就是传递给HTTP服务器而不是SSH服务器的呢?
是凭借端口号吗?当报文到达服务器时,是端口号来区分不同应用程序的,所以应该借助端口号来区分。
举个例子反驳一下cxuan,假如到达服务器的两条数据都是由80端口发出的你该如何区分呢?或者说到达服务器的两条数据端口一样,协议不同,该如何区分呢?
所以仅凭端口号来确定某一条报文显然是不够的。
互联网上一般使用源IP地址、目标IP地址、源端口号、目标端口号来进行区分。如果其中的某一项不同,就被认为是不同的报文段。这些也是多路分解和多路复用的基础。

确定端口号

在实际通信之前,需要先确定一下端口号,确定端口号的方法分为两种:
  • 标准既定的端口号
标准既定的端口号是静态分配的,每个程序都会有自己的端口号,每个端口号都有不同的用途。端口号是一个16比特的数,其大小在0-65535 之间,0-1023 范围内的端口号都是动态分配的既定端口号,例如HTTP使用80端口来标识,FTP使用21端口来标识,SSH使用22来标识。这类端口号有一个特殊的名字,叫做周知端口号(Well-Known Port Number)。
  • 时序分配的端口号
第二种分配端口号的方式是一种动态分配法,在这种方法下,客户端应用程序可以完全不用自己设置端口号,凭借操作系统进行分配,操作系统可以为每个应用程序分配互不冲突的端口号。这种动态分配端口号的机制即使是同一个客户端发起的TCP连接,也能识别不同的连接。

多路复用和多路分解

我们上面聊到了在主机上的每个套接字都会分配一个端口号,当报文段到达主机时,运输层会检查报文段中的目的端口号,并将其定向到相应的套接字,然后报文段中的数据通过套接字进入其所连接的进程。下面我们来聊一下什么是多路复用和多路分解的概念。
多路复用和多路分解分为两种,即无连接的多路复用(多路分解)和面向连接的多路复用(多路分解)

无连接的多路复用和多路分解

开发人员会编写代码确定端口号是周知端口号还是时序分配的端口号。假如主机 A中的一个10637端口要向主机B中的45438端口发送数据,运输层采用的是 UDP协议,数据在应用层产生后,会在运输层中加工处理,然后在网络层将数据封装得到IP数据报,IP数据包通过链路层尽力而为的交付给主机B,然后主机 B会检查报文段中的端口号判断是哪个套接字的。
这一系列的过程如下所示:
UDP套接字就是一个二元组,二元组包含目的IP地址和目的端口号。
所以,如果两个UDP报文段有不同的源IP地址和/或相同的源端口号,但是具有相同的目的IP地址和目的端口号,那么这两个报文会通过套接字定位到相同的目的进程。
这里思考一个问题,主机A给主机B发送一个消息,为什么还需要知道源端口号呢?比如我给妹子表达出我对你有点意思的信息,妹子还需要知道这个信息是从我的哪个器官发出的吗?知道是我这个人对你有点意思不就完了?实际上是需要的,因为妹子如果要表达出她对你也有点意思,她是不是可能会亲你一口,那她得知道往哪亲吧?
这就是,在A到B的报文段中,源端口号会作为 返回地址 的一部分,即当B需要回发一个报文段给A时,B需要从A到B中的源端口号取值,如下图所示

面向连接的多路复用与多路分解

如果说无连接的多路复用和多路分解指的是UDP的话,那么面向连接的多路复用与多路分解指的是TCP了,TCP和UDP在报文结构上的差别是,UDP是一个二元组而TCP是一个四元组,即源IP地址、目标IP地址、源端口号、目标端口号 ,这个我们上面也提到了。当一个TCP报文段从网络到达一台主机时,这个主机会根据这四个值拆解到对应的套接字上。
上图显示了面向连接的多路复用和多路分解的过程,图中主机C向主机B发起了两个HTTP请求,主机A向主机C发起了一个HTTP请求,主机A、B、C都有自己唯一的P地址,当主机C发出HTTP请求后,主机B能够分解这两个HTTP连接,因为主机C发出请求的两个源端口号不同,所以对于主机B来说,这是两条请求,主机B能够进行分解。对于主机A和主机C来说,这两个主机有不同的IP地址,所以对于主机B来说,也能够进行分解。


UDP

终于,我们开始了对UDP协议的探讨,淦起!
UDP的全称是用户数据报协议(UDP,User Datagram Protocol),UDP为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。如果应用程序开发人员选择的是UDP而不是TCP的话,那么该应用程序相当于就是和IP 直接打交道的。
从应用程序传递过来的数据,会附加上多路复用/多路分解的源和目的端口号字段,以及其他字段,然后将形成的报文传递给网络层,网络层将运输层报文段封装到IP数据报中,然后尽力而为的交付给目标主机。最关键的一点就是,使用 UDP协议在将数据报传递给目标主机时,发送方和接收方的运输层实体间是没有握手的。正因为如此,UDP被称为是无连接的协议。

UDP特点

UDP协议一般作为流媒体应用、语音交流、视频会议所使用的传输层协议,我们大家都知道的DNS协议底层也使用了UDP协议,这些应用或协议之所以选择 UDP主要是因为以下这几点
  • 速度快,采用UDP协议时,只要应用进程将数据传给UDP,UDP就会将此数据打包进UDP报文段并立刻传递给网络层,然后TCP有拥塞控制的功能,它会在发送前判断互联网的拥堵情况,如果互联网极度阻塞,那么就会抑制TCP的发送方。使用UDP的目的就是希望实时性。
  • 无须建立连接,TCP在数据传输之前需要经过三次握手的操作,而UDP 则无须任何准备即可进行数据传输。因此UDP没有建立连接的时延。如果使用TCP和UDP来比喻开发人员:TCP就是那种凡事都要设计好,没设计不会进行开发的工程师,需要把一切因素考虑在内后再开干!所以非常靠谱;而UDP就是那种上来直接干干干,接到项目需求马上就开干,也不管设计,也不管技术选型,就是干,这种开发人员非常不靠谱,但是适合快速迭代开发,因为可以马上上手!
  • 无连接状态,TCP需要在端系统中维护连接状态,连接状态包括接收和发送缓存、拥塞控制参数以及序号和确认号的参数,在UDP中没有这些参数,也没有发送缓存和接受缓存。因此,某些专门用于某种特定应用的服务器当应用程序运行在 UDP上,一般能支持更多的活跃用户
  • 分组首部开销小,每个TCP报文段都有20字节的首部开销,而UDP仅仅只有8字节的开销。
这里需要注意一点,并不是所有使用UDP协议的应用层都是不可靠的,应用程序可以自己实现可靠的数据传输,通过增加确认和重传机制。所以使用 UDP协议最大的特点就是速度快。

UDP报文结构

下面来一起看一下UDP的报文结构,每个UDP报文分为UDP报头和UDP数据区两部分。报头由4个16位长(2字节)字段组成,分别说明该报文的源端口、目的端口、报文长度和校验值。
  • 源端口号(Source Port) :这个字段占据UDP报文头的前16位,通常包含发送数据报的应用程序所使用的UDP端口。接收端的应用程序利用这个字段的值作为发送响应的目的地址。这个字段是可选项,有时不会设置源端口号。没有源端口号就默认为0 ,通常用于不需要返回消息的通信中。
  • 目标端口号(Destination Port): 表示接收端端口,字段长为16位
  • 长度(Length): 该字段占据16位,表示UDP数据报长度,包含UDP报文头和UDP数据长度。因为UDP报文头长度是8个字节,所以这个值最小为8,最大长度为65535字节。
  • 校验和(Checksum):UDP使用校验和来保证数据安全性,UDP的校验和也提供了差错检测功能,差错检测用于校验报文段从源到目标主机的过程中,数据的完整性是否发生了改变。发送方的UDP对报文段中的16比特字的和进行反码运算,求和时遇到的位溢出都会被忽略,比如下面这个例子,三个16比特的数字进行相加
这些16比特的前两个和是
然后再将上面的结果和第三个16比特的数进行相加
最后一次相加的位会进行溢出,溢出位1要被舍弃,然后进行反码运算,反码运算就是将所有的1变为0,0变为1。因此1000 0100 1001 0101的反码就是0111 1011 0110 1010,这就是校验和,如果在接收方,数据没有出现差错,那么全部的4个16比特的数值进行运算,同时也包括校验和,如果最后结果的值不是1111 1111 1111 1111的话,那么就表示传输过程中的数据出现了差错。
下面来想一个问题,为什么UDP会提供差错检测的功能?
这其实是一种 “端到端” 的设计原则,这个原则是要让传输中各种错误发生的概率降低到一个可以接受的水平。
文件从主机A传到主机B,也就是说AB主机要通信,需要经过三个环节:首先是主机A从磁盘上读取文件并将数据分组成一个个数据包packet,,然后数据包通过连接主机A和主机B的网络传输到主机B,最后是主机B收到数据包并将数据包写入磁盘。在这个看似简单其实很复杂的过程中可能会由于某些原因而影响正常通信。比如:磁盘上文件读写错误、缓冲溢出、内存出错、网络拥挤等等这些因素都有可能导致数据包的出错或者丢失,由此可见用于通信的网络是不可靠的。
由于实现通信只要经过上述三个环节,那么我们就想是否在其中某个环节上增加一个检错纠错机制来用于对信息进行把关呢?
网络层肯定不能做这件事,因为网络层的最主要目的是增大数据传输的速率,网络层不需要考虑数据的完整性,数据的完整性和正确性交给端系统去检测就行了,因此在数据传输中,对于网络层只能要求其提供尽可能好的数据传输服务,而不可能寄希望于网络层提供数据完整性的服务。
UDP不可靠的原因是它虽然提供差错检测的功能,但是对于差错没有恢复能力更不会有重传机制。
更多精彩推荐
挑战 TensorFlow、PyTorch,谁才是中国 AI 开源框架之星?
买房必看!又一程序员自编“购房宝典”火爆 GitHub
Google回应全球宕机:磁盘满了;摩拜App昨晚正式停止服务;Docker Desktop 3.0.0发布|极客头条
为什么苹果M1芯片这么快?
我是Redis,MySQL大哥被我害惨了!
“刚毕业1年,做数据分析凭什么涨薪三连跳!”网友:吹的不多..
点分享点点赞点在看

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存