【网络】传输层协议——UDP协议

 1.传输层协议

首先我们要明白,进入传输层,也就进入了操作系统内核。

因为应用层下面这几个就是操作系统内部

我们学传输层及其以下几层,也就是在学操作系统内核的网络模块。 

1.1.传输层协议

       传输层能够实现端到端的连接。比如说我们用QQ与别人发信息,网络层能够将信息发送到对方的主机上,主机上使用什么协议来接受这个信息就由传输层来完成,所以传输层实现的是进程到进程间的连接。

        传输层提供的是应用程序间的逻辑通信,也就是说它向高层(应用层)屏蔽了下面网络层的细节,使应用程序看起来好像是在传输层之间沿着水平方向传输数据,但事实上两者之间并没有这样一条实际的物理连接。

传输层的作用:

  1. 为上面的应用层提供通信服务,提供应用进程间的逻辑通信
  2. 在 OSI 七层参考模型中,传输层是 面向通信部分的最高层,用户功能中的最底层
  3. 两大重要的功能:1)复用,2)分用。复用是指,在发送端,多个应用进程共用一个传输层。分用是指,在接收端,传输层会根据端口号将数据分派给不同的应用进程
  4. 传输层和网络层的区别:1)网络层为不同主机提供通信服务,而传输层为不同主机的不同应用提供通信服务;2)网络层只对报文头部进行差错检测,而传输层对整个报文进行差错检测

常见的传输层协议有以下两种

  1.  TCP协议:面向连接的可靠传输协议。利用TCP进行通信时,首先要通过三步握手,以建立通信双方的连接。TCP提供了数据的确认和数据重传的机制,保证发送的数据一定能到达通信的对方。
  2.  UDP协议:是无连接的,不可靠的传输协议。采用UDP进行通信时不用建立连接,可以直接向一个IP地址发送数据,但是不能保证对方是否能收到。

2.再谈端口号

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

如图所示,在一个机器上运行着许多进程,每个进程使用的协议都不一样,比如FTP,SSH,SMTP,HTTP,FTP等。

        正是因为每一个进程都有自己的端口号(如图中TCP21,TCP22这些,后面的数字就是端口号)当发来的数据从网络中传输到应用层后,在传输层就会提取出该数据对应的目的端口号,进而确定该数据应该交付给当前主机上的哪一个服务进程。 

2.1.五元组

TCP/IP协议是传输数据常用的协议,它会用 “源IP”、“源端口号”、“目的IP”、“目的端口号”、“协议号” 这样一个五元组来标识一个通信。

  • 协议号是什么

        IP是网络层协议,IP头中的协议号用来说明IP报文中承载的是哪种协议(一般是传输层协议,比如6 TCP,17 UDP;但也可能是网络层协议,比如1 ICMP;也可能是应用层协议,比如89 OSPF)。

        TCP/UDP是传输层协议,TCP/UDP的端口号用来说明是哪种上层应用,比如TCP 80代表WWW,TCP 23代表Telnet,UDP 69代表TFTP。

        目的主机收到IP包后,根据IP协议号确定送给哪个模块(TCP/UDP/ICMP...)处数据由一台主机传输至另一台主机时,根据IP头上的协议号将数据送到相应的传输层解析(IP头被过滤掉,即此时到达传输层的数据已经没有IP头));

        送给TCP/UDP模块的报文根据端口号确定送给哪个应用程序处理。【根据端口号确定数据送达主机中的应用程序,当数据送至应用程序(可以理解为主机中的某个进程)时,TCP/UDP头被过滤掉】

  • 协议号和端口号的区别
  1. 协议号是存在于IP报头当中的,其长度是8位。协议号指明了数据报所携带的数据是使用的何种协议,以便让目的主机的IP层知道应该将该数据交付给传输层的哪个协议进行处理。
  2. 端口号是存在于UDP和TCP报头当中的,其长度是16位。端口号的作用是唯一标识一台主机上的某个进程
  3. 协议号是作用于传输层和网络层之间的,而端口号是作用于应用层于传输层之间的。

        如上图所示,客户端A打开了两个浏览器页面(可认为是两个进程)向服务器分别发送数据1和数据2。所以在这两份数据的五元组中,源IP地址都是客户端A的IP地址,目标IP地址都是服务器的IP地址,源端口号分别是2001和2002,它们能标识这两个进程,目标端口号都是服务端的HTTP进程,HTTP使用的端口号是80。

        客户端B也打开一个浏览器页面(可认为是一个进程)向服务器发送的数据3。所以在这份数据的五元组中,源IP地址就是客户端B的IP地址,目标IP是服务端的IP地址,源端口号是这个页面进程的端口号,目标端口号是服务器HTTP进程的端口号80。

        协议号是通信协议的编号,指定的协议号可以标识一种协议,比如6号就标识TCP协议。

        IP地址属于IP协议的内容,IP协议属于网络层,在传输层处不做讨论。

2.2.端口号的划分

端口号的长度是16位,因此端口号的范围是0 ~ 65535:

  • 0 ~ 1023知名端口号。比如HTTP,FTP,SSH等这些广为使用的应用层协议,它们的端口号都是固定的。
  • 1024 ~ 65535:操作系统动态分配的端口号。客户端程序的端口号就是由操作系统从这个范围分配的。

2.2.1.认识知名端口号

        0~1023范围内的端口号叫做知名端口号,这些端口号常被固定的服务绑定,如HTTP、FTP、SSH等这些广为使用的应用层协议,他们的端口号都是固定的。由于应用层协议本质上是进程在使用,所以在这里应用层协议、进程可以看作是等价的。

        可以这样理解,110对应的是报警电话,120对应的是急救电话,119是火警电话,这些号码已经跟这些公共服务强绑定了。这些放在端口号上也一样,0~1023这些端口号都有对应的协议,不能随便更改,就像报警电话不会随便更改一样。所以,我们自己写程序时,要避开0~1023这些知名端口号。

        有些服务器是非常常用的,为了使用方便,人们约定了一些常用的服务器,都是用以下这些固定的端口号:

  • ssh服务器,使用22端口
  • ftp服务器,使用21端口
  • telnet服务器,使用23端口
  • http服务器,使用80端口。
  • https服务器,使用443端口

在Linux机器中,有一个文件/etc/services,它存储了所有具体端口号对应的服务协议。

我截取了该文件的一部分,可以清楚地看到http的端口号是80。

我们也可以看到,这个80端口支持tcp,udp,sctp三种协议!!!

接下来来问两个问题

  • 1.一个端口号是否可以被多个进程绑定?

         一个端口号绝对不能被多个进程绑定,因为端口号的作用就是唯一标识一个进程,如果绑定一个已经被绑定的端口号,就会出现绑定失败的问题。

  • 2.一个进程是否可以绑定多个端口号? 

        一个进程是可以绑定多个端口号的,这与“端口号必须唯一标识一个进程”是不冲突的,只不过现在这多个端口唯一标识的是同一个进程罢了。

        我们限制的是从端口号到进程的唯一性,而没有要求从进程到端口号也必须满足唯一性,因此一个进程是可以绑定多个端口号的。

 2.3.netstat命令

netstat [-acCeFghilMnNoprstuvVwx][-A<网络类型>][--ip]

功能:可用于查看当前机器的网络状态。 

  1. n 拒绝显示别名,能显示数字的全部转化成数字
  2. l 仅列出有在 Listen ( 监听 ) 的服务状态
  3. p 显示建立相关链接的程序名
  4. t (tcp) 仅显示 tcp 相关选项
  5. u (udp) 仅显示 udp 相关选项
  6. a (all) 显示所有选项,默认不显示 LISTEN 相关

我们演示一下

这个最常用的就是下面这两个

查看监听的TCP端口

netstat -ntlp

查看监听的UDP端口

netstat -nulp

就不演示了

netstat命令是一个用于显示网络连接、路由表、接口统计、伪装连接以及多播成员等信息的工具。当使用netstat命令时,其输出结果包含多个列,每列都代表了不同的信息。以下是对netstat输出中常见列的含义的解释:

  • 1. Proto:含义:显示连接使用的协议,如TCP或UDP。
  • 2. Local Address含义:表示本地计算机的IP地址和端口号。这个地址是服务正在监听的地址。对于监听所有IP地址的服务,该列会显示为0.0.0.0:<端口号>。对于只监听特定IP地址的服务,将显示该IP地址和端口号。
  • 3. Foreign Address含义:表示远程计算机的IP地址和端口号。这个地址是与本地计算机建立连接的远程计算机的地址。如果远程地址未知或未指定,则可能显示为0.0.0.0:*或*:*。
  • 4. State:含义:表示TCP连接的状态。TCP连接状态包括多种,如LISTEN(监听状态,等待进入连接队列的连接请求)、ESTABLISHED(已建立连接,表示双方已经开始数据传输)、TIME_WAIT(等待足够的时间以确保远程TCP接收到连接中断请求的确认)等。UDP协议是无连接的,因此UDP连接不会显示状态。
  • 5. Recv-Q:含义:表示接收队列中的连接个数。这是指已经到达本地计算机但尚未被应用程序读取的数据量。对于TCP连接,这个值通常应该很小或为零,因为TCP会尽量保持接收队列为空。
  • 6. Send-Q:含义:表示发送队列中的字节数。这是指已经由本地计算机发送但尚未被远程计算机确认的数据量。对于TCP连接,这个值也应该很小或为零,除非正在传输大量数据。
  • 7. PID/Program name(可选):含义:在某些netstat命令的输出中,还会显示创建该连接的进程的PID(进程ID)和程序名称。这有助于识别哪个程序正在使用特定的端口或IP地址。

以下是一个简化的netstat命令输出示例,用于说明上述列的含义:

Proto  Local Address          Foreign Address        State       Recv-Q Send-Q  PID/Program name  
TCP    0.0.0.0:22             0.0.0.0:*              LISTEN      0      128     -  
TCP    192.168.1.100:80       192.168.1.20:51234      ESTABLISHED 0      0       1234/nginx  
UDP    0.0.0.0:68             *:*                                0      0       -

在这个示例中:

  1. 第一行表示本地计算机上的SSH服务(端口22)正在监听所有IP地址。
  2. 第二行表示一个TCP连接,本地计算机的IP地址是192.168.1.100,端口是80,与远程计算机192.168.1.20的端口51234建立了连接,状态是ESTABLISHED。
  3. 第三行表示一个UDP连接,本地计算机正在监听所有IP地址的68端口,但没有远程地址信息,因为UDP是无连接的。

2.4.pidof命令

pidof是一个用于查找指定名称的进程的进程ID的工具。

语法为:pidof [进程名]

pidof命令可以通过查询运行中的进程信息来找到特定进程的进程ID。它可以返回一个或多个进程ID,这些ID可以用于进一步确认进程的运行状态、杀掉进程或者发送一个信号给它。

选项和参数:

  • -s:仅返回一个进程号。
  • -c:仅显示具有相同根目录的进程。
  • -x:显示由脚本开启的进程。
  • -o:指定不显示的进程ID。


使用pidof命令查找特定进程的ID时,只需要将进程名作为参数传递给pidof命令即可。

例如,要查找名为nginx的进程的ID,可以运行命令pidof nginx

常见搭配kill命令使用,例如 kill -9 $(pidof nginx)

3.UDP协议报头

现在开始我们就正式进入传输层的学习。

从现在开始,进入操作系统内核部分,我们在学习传输层以及他下面每一层协议的时候,都要搞清楚几件事。

  1. 报头和有效载荷如何分离有效载荷应该上交给哪一个上层协议(对应的协议字段,方案)——这个是最重要的
  2. 认识报头
  3. 学习该协议周边的问题

 3.1.UDP协议格式

下图就是UDP协议的格式,前八个字节属于协议报头,之后的是有效载荷。

UDP协议的前八个字节作为报头,64个比特位。其中这八个字节的前16位0-15存放的是源端口号,第16~31位存放目的端口号,32~47位存放的是UDP的长度,48~64位存放的是UDP校验和。

  1. 16位源端口号:表示发送端(客户端)的端口号。
  2. 16位目的端口号:表示接收端(服务端)的端口号。
  3. 16位UDP长度:整个UDP数据段(报文)的长度,这个长度是包括报头和有效载荷的长度,UDP数据段最大64KB。
  4. 16位校验和:由操作系统进行校验,如果发送方和接收方的校验和不一致,说明出错,就会丢弃整个UDP数据段。(不可靠的部分)

我们注意到, UDP协议首部中有一个16位的最大长度. 说的是一个UDP能传输的最大报文长度是2^16 --> 2^10 * 2^6=64K(包括UDP首部)

然而64K在当今的互联网环境下, 是一个非常小的数字.
        UDP数据段最长不能超过64KB,所以如果传输的数据超过了64KB,就需要用户在应用层手动将数据分成多个大小小于等于64KB的数据包,并进行多次发送,在接收端用户也需要手动将各分包重新拼装。

 每层协议的学习,我们都应该掌握这两个问题:

  1. 协议报头和有效载荷如何分离?
  2. 有效载荷如何向上交付?
  • 第一个问题,协议报头和有效载荷如何分离。

        我们看到,UDP首部有一个16位的UDP长度。16位UDP长度:整个UDP数据段(报文)的长度,这个长度是包括报头和有效载荷的长度,UDP数据段最大64KB。

通过首部长度,我们就可以将TCP首部和有效载荷分离。

  • 第二个问题,有效载荷如何向上交付。

UDP是传输层的,上层是应用层。而应用层程序会绑定端口号,UDP首部中有16位目的端口号,根据端口号做到向上交付。

3.2.UDP在Linux的表现形式

        UDP协议是传输层协议,由操作系统维护,而Linux操作系统又是由C语言写的,所以UDP的报头一定会在操作系统中有自己的结构化数据,我们如果自己写一个结构体描述差不多就是这样:

struct udp_hdr
{
    uint16_t src_port;//16位源端口号
    uint16_t dest_port;//16位目的端口号
    uint16_t length;//16位UDP长度
    uint16_t check;//16位UDP校验和
};

当然将各变量改为unsigned int src_port:16;这样的结构也可以。

        上面就是一个简易的UDP报文。

        由于可能会有很多进程都在使用UDP向对方发送消息,由于一定会存在大量的UDP报文,操作系统一定要把它管理起来,怎么管理?

        先描述,在组织。操作系统会有一个struct sk_buff来管理这些UDP报文。

struct sk_buff
{
    char* start;
    char* end;
    char* pos;
    int type;
    //..... 省略
    struct sk_buff* _Next; // 指向下一个,像链表一样串起来
};

  • 理解报头与有效数据的分离

        以C语言的知识理解报头与有效数据的分离,我们可以认为当计算机接收UDP数据时,该数据会先放在操作系统的已创建的内核缓冲区内(最大为64KB)。

        然后创建一个hdr指针指向UDP报头起始位置,再再偏移报头字节数(sizeof(udp_hdr))构建一个start指针指向内核中有效载荷的起始位置。

        最后根据报头的结构化数据将数据将各个数据(包括正文内容)拷贝到内存,这样就能实现了分离。

我们说填充报头,就是下面这个伪代码这样子。

char* hdr = malloc(XXX);//操作系统创建内核缓冲区
char* start = hdr + sizeof(struct udr_hdr);//指向有效载荷
strcpy(start,buffer,len);//将用户缓冲区中数据复制到内核缓冲区有效载荷处。
(struct udp_hdr*)hdr->src_port = xxx;//赋值源端口
(struct udp_hdr*)hdr->dest_port = xxx;//赋值源端口
(struct udp_hdr*)hdr->length = xxx;//赋值源端口
(struct udp_hdr*)hdr->check = xxx;//赋值源端口

  • 分用

         分用时,操作系统中已经维护了一个哈希表,使用绑定的端口号作为key值,就能直接找到这个进程,将有效载荷交给进程即可。所以,进程只要使用网络就一定要绑定端口号,不管是否使用bind显式绑定,这个绑定的过程本质上就是将数据插入哈希表。

3.3.UDP是面向数据报的

  • 什么是面向数据报的?

        面向数据报可以理解成面向快递,你的朋友给你寄了一个、两个、三个快递,未来你在收的一定是一个、两个、三个快递。你朋友发了三个快递你一定是收三个快递,不会收半个、一个半、两个等,他发几个你就收几个。

        客户端曾经发了一个报文,你在调用recvfrom成功的时候,这个函数必定把一个完整的报文全部读上来。这叫做UDP数据报。

  1. 其一,在写udp的代码时明显可以感觉到不像写tcp网页版计数器哪里首先必须要先读到一个完整的报文,在udp哪里从来没有说过这样的话,因为用udp直接可以保证读到一个完整报文。
  2. 其二,对方调sendto发送10次报文,对方必须调用recvfrom接收10次报文,次数是1:1的。

这就是面向数据报,使用UDP协议我们不用考虑在应用层enlength增加报头,delength删除报头,用来区分数据,只用考虑序列化和反序列化就可以了。

应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并;

用UDP传输100个字节的数据:

  • 如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节
  • 2.为什么 UDP 是面向报文的协议?

        当用户消息通过 UDP 协议传输时,操作系统不会对消息进行拆分,在组装好 UDP 头部后就交给网络层来处理,所以发出去的 UDP 报文中的数据部分就是完整的用户消息,也就是每个 UDP 报文就是一个用户消息的边界,这样接收方在接收到 UDP 报文后,读一个 UDP 报文就能读取到完整的用户消息。

  • 你可能会问,如果收到了两个 UDP 报文,操作系统是怎么区分开的?

操作系统在收到 UDP 报文后,会将其插入到队列里,队列里的每一个元素就是一个 UDP 报文,这样当用户调用 recvfrom() 系统调用读数据的时候,就会从队列里取出一个数据,然后从内核里拷贝给用户缓冲区。

3.4.TCP的缓冲区和UDP的缓冲区

  • TCP的缓冲区

        还记得之前用过的send/sendto、recv/recvfrom这样的的接口吗?这些系统调用直接就能把数据发出去了吗?

        结论当然是否定的系统调用的工作在操作系统和用户应用的界限之间,而在网络的分层模型中,数据会一层一层向下传递并在物理层发出,所以这些系统调用的任务一定是从应用层将数据向操作系统传递。

        在TCP协议中,操作系统会为通信双方各自维护一个发送缓冲区,一个接收缓冲区。用户层在调用send/sendto函数之后,操作系统会自动对需要发送的内容拼接TCP报头(TCP报头的具体内容后面会讲)形成数据段,然后拷贝到到发送缓冲区中。

        同样,本主机从对端主机收到数据段后,也会先将其放入接收缓冲区中。当用户层读使用recv/recvfrom这样的系统调用时,系统调用就会将接收缓冲区内的数据拷贝到应用层中,具体点说就是拷贝到接收数据的字符串中。

        所以,send/sendto、recv/recvfrom本质上是一个拷贝接口,它们前者负责将应用层的数据拷贝到发送缓冲区,后者负责将数据从接收缓冲区拷贝到应用层。

        在两个缓冲区中的数据由TCP的内部代码进行发送和接收,用户只负责将数据放入或拿出这两个缓冲区。正是因为数据的传输无需用户参与,所以TCP的全名叫做传输控制协议。

        而且这两个缓冲区使得通信双方进程在同一时刻,既可以发送数据,也可以接收数据,且互不影响,我们称之为全双工。

  • 2.UDP的缓冲区

        UDP协议与TCP相似,只是少了一个发送缓冲区。当客户端使用UDP协议将用户层的数据使用sendto发送的时候,数据会被直接拷贝到内核中,经过一定处理后立即发出。

        在使用UDP协议接收数据时,操作系统将发送来的数据存放在接收缓冲区中,在适当的时候,操作系统会将数据交给用户层。

        UDP的信息传输方式也实现了发送和接收数据同时进行和互不干扰,也叫做全双工。

3.5.基于UDP的应用层协议

我们不会单独说UDP协议有啥用,因为他是操作系统内部的东西,类似于底层的东西,存在的意义就是为了给上层提供服务。UDP的存在,可以让上层的应用层有各式各样的协议。

缩写

全名

NFS

网络文件系统

TFTP

简单文件传输协议

DHCP

动态主机配置协议

BOOT

启动协议(用于无盘设备启动)

DNS

域名解析协议

上面这些协议在传输层都是使用的UDP协议,其中很多我们天天都在用。

UDP我们就简单的讲一下就好了,这个不是我们的重点,我们的重点是TCP协议

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值