1.传输层

在学习HTTP等应用层协议时,为了便于理解,可以简单的认为HTTP协议是将请求和响应直接发送到了网络当中。但实际应用层需要先将数据交给传输层,由传输层对数据做进一步处理后再将数据继续向下进行交付,该过程贯穿整个网络协议栈,最终才能将数据发送到网络当中。

传输层负责可靠性传输,确保数据能够可靠地传送到目标地址。为了方便理解,在学习传输层协议时也可以简单的认为传输层协议是将数据直接发送到了网络当中。

1.1.再谈端口号

端口号的作用:

端口号(Port)标识一个主机上进行网络通信的不同的应用程序。当主机从网络中获取到数据后,需要自底向上进行数据的交付,而这个数据最终应该交给上层的哪个应用处理程序,就是由该数据当中的目的端口号来决定的。

从网络中获取的数据在进行向上交付时,在传输层就会提取出该数据对应的目的端口号,进而确定该数据应该交付给当前主机上的哪一个服务进程。

Linux - 第18节 - 网络基础(传输层)

因此端口号是属于传输层的概念的,在传输层协议的报头当中就会包含与端口相关的字段。

五元组标识一个通信:

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

比如有多台客户端主机同时访问服务器,这些客户端主机上可能有一个客户端进程,也可能有多个客户端进程,它们都在访问同一台服务器。

而这台服务器就是通过“源IP地址”,“源端口号”,“目的IP地址”,“目的端口号”,“协议号”来识别一个通信的。

• 先提取出数据当中的目的IP地址和目的端口号,确定该数据是发送给当前服务进程的。
• 然后提取出数据当中的协议号,为该数据提供对应类型的服务。
• 最后提取出数据当中的源IP地址和源端口号,将其作为响应数据的目的IP地址和目的端口号,将响应结果发送给对应的客户端进程。

Linux - 第18节 - 网络基础(传输层)

通过 netstat -nltp 命令可以查看到这样的五元组信息,如下图所示,其中的Local Address表示的就是源IP地址和源端口号,Foreign Address表示的就是目的IP地址和目的端口号,而Proto表示的就是协议类型。

Linux - 第18节 - 网络基础(传输层)

协议号 VS 端口号:

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

1.2.端口号范围划分

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

• 0 ~ 1023:知名端口号。比如HTTP,FTP,SSH等这些广为使用的应用层协议,它们的端口号都是固定的。

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

1.3.认识知名端口号

常见的知名端口号:

有些服务器是非常常用的,这些服务器的端口号一般都是固定的:

• ssh,服务器使用22端口。

• ftp,服务器使用21端口。

• telnet,服务器使用23端口。

• http,服务器使用80端口。

• https,服务器使用443端口。

查看知名端口号:

在/etc/services路径下有一个services配置文件,该文件中列举了常见服务与其匹配的端口号,如下图所示。

文件中的每一行对应一种服务,它由4个字段组成,每个字段之间用TAB或空格分隔,分别表示“服务名称”、“使用端口”、“协议名称”以及“别名”。

Linux - 第18节 - 网络基础(传输层)

1.4.两个问题

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

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

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

1.5.netstat命令

netstat命令:

功能:netstat是一个用来查看网络状态的重要工具。

常见选项:

• n:拒绝显示别名,能显示数字的全部转换成数字。
• l:仅列出处于LISTEN(监听)状态的服务。
• p:显示建立相关链接的程序名。
• t(tcp):仅显示tcp相关的选项。
• u(udp):仅显示udp相关的选项。
• a(all):显示所有的选项,默认不显示LISTEN相关。
注:

1.查看TCP相关的网络信息时,一般选择使用nltp组合选项。

Linux - 第18节 - 网络基础(传输层)

2.而查看UDP相关的网络信息时,一般选择使用nlup组合选项。

Linux - 第18节 - 网络基础(传输层)

3.如果想查看LISTEN状态以外的连接信息,可以去掉l选项,此时就会将处于其他状态的连接信息显示出来。

Linux - 第18节 - 网络基础(传输层)

1.6.pidof命令

pidof命令可以通过进程名,查看进程id。

Linux - 第18节 - 网络基础(传输层)

注:pidof命令可以配合kill命令快速杀死一个进程。

Linux - 第18节 - 网络基础(传输层)

2.UDP协议

2.1.UDP协议格式

UDP协议的位置:

网络套接字编程时用到的各种接口,是位于应用层和传输层之间的一层系统调用接口,这些接口是系统提供的,我们可以通过这些接口搭建上层应用,比如HTTP。我们经常说HTTP是基于TCP的,实际就是因为HTTP在TCP套接字编程上搭建的。

而socket接口往下的传输层实际就是由操作系统管理的,因此UDP是属于内核当中的,是操作系统本身协议栈自带的,其代码不是由上层用户编写的,UDP的所有功能都是由操作系统完成,因此网络也是操作系统的一部分。

UDP协议格式:

UDP报文前8个字节是UDP报头,后面的数据是UDP有效载荷。

• 16位源端口号:表示数据从哪里来。
• 16位目的端口号:表示数据要到哪里去。
• 16位UDP长度:表示整个数据报文(UDP首部/报头+UDP数据/有效载荷)的长度。
• 16位UDP检验和:如果UDP报文的检验和出错,就会直接将报文丢弃。
注:我们在应用层看到的端口号大部分都是16位的,其根本原因就是因为传输层协议当中的端口号就是16位的。

Linux - 第18节 - 网络基础(传输层)

问题1:UDP如何将报头与有效载荷进行分离(如何封装和解包)?

答:UDP的报头当中只包含四个字段,每个字段的长度都是16位,总共8字节。因此UDP采用的实际上是一种定长报头,UDP在读取报文时读取完前8个字节后剩下的就都是有效载荷了。

问题2:UDP如何决定应该将有效载荷交付给上层的谁(如何分用)?

答:UDP上层也有很多应用层协议,因此UDP必须想办法将有效载荷交给对应的上层协议,也就是交给应用层对应的进程。

应用层的每一个网络进程都会绑定一个端口号,服务端进程必须显示绑定一个端口号,客户端进程则是由系统动态绑定的一个端口号。UDP就是通过报头当中的目的端口号来找到对应的应用层进程的。
注:内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号得到对应的进程ID,进而找到对应的应用层进程。

理解报头:

操作系统是C语言写的,而UDP协议又是属于内核协议栈的,因此UDP协议也一定是用C语言编写的,UDP报头实际就是一个位段类型,如下图一所示,而添加报头的本质,其实就是将报头对象拷贝到有效载荷数据前面的过程,如下图二所示。

Linux - 第18节 - 网络基础(传输层)

Linux - 第18节 - 网络基础(传输层)

UDP数据封装:

• 当应用层将数据交给传输层后,在传输层就会创建一个UDP报头类型的变量,然后填充报头当中的各个字段,此时就得到了一个UDP报头。
• 此时操作系统再在内核当中开辟一块空间,将UDP报头和有效载荷拷贝到一起,此时就形成了UDP报文。
UDP数据分用:

• 当传输层从下层获取到一个报文后,就会读取该报文的钱8个字节,提取出对应的目的端口号。
• 通过目的端口号找到对应的上层应用层进程,然后将剩下的有效载荷向上交付给该应用层进程。

2.2.UDP协议的特点

UDP传输的过程就类似于寄信,其特点如下:

• 无连接:知道对端的IP和端口号就直接进行数据传输,不需要建立连接。
• 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。不可靠也意味着不需要为了可靠性做一系列工作,意味着UDP很简单,易维护。
• 面向数据报:不能够灵活的控制读写数据的次数和数量(具体后面介绍)。

2.3.面向数据报

应用层交给UDP多长的报文,UDP就原样发送,既不会拆分,也不会合并,这就叫做面向数据报。

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

注:对于UDP,报文和报文之间是有明显边界的,发送端发送了几次,接收端就要接收几次。对于TCP,发送端发送次数和接收次数可能不同,发送端发送了五次,接收端可能一次就读完了,发送端发送十次,接收端可能一百次才能读完。

2.4.UDP的缓冲区

• UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。
• UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃。
• UDP的socket既能读,也能写,因此UDP是全双工的。

注:

1.真正调用write、send等接口发送数据时,并没有把数据发送到网络中,而是把数据交给了操作系统(我们并不知道网络情况,只有操作系统了解),操作系统帮助我们将数据发送出去。

所以我们之前讲的所有网络或文件相关接口,实际上不能叫做类似发送或写入接口,而应该叫做拷贝函数,拷贝到操作系统对应的缓冲区中,操作系统根据自己的策略执行某些操作。

2.报文在网络中进行路由转发时,并不是每一个报文选择的路由路径都是一样的,因此报文发送的顺序和接收的顺序可能是不同的。

问题:为什么UDP要有接收缓冲区?

答:如果UDP没有接收缓冲区,那么就要求上层及时将UDP获取到的报文读取上去,如果一个报文在UDP没有被读取,那么此时UDP从底层获取上来的报文数据就会被迫丢弃。

一个报文从一台主机传输到另一台主机,在传输过程中会消耗主机资源和网络资源。如果UDP收到一个报文后仅仅因为上次收到的报文没有被上层读取,而被迫丢弃一个可能并没有错误的报文,这就是在浪费主机资源和网络资源。

因此UDP本身是会维护一个接收缓冲区的,当有新的UDP报文到来时就会把这个报文放到接收缓冲区当中,此时上层在读数据的时就直接从这个接收缓冲区当中进行读取就行了,而如果UDP接收缓冲区当中没有数据那上层在读取时就会被阻塞。因此UDP的接收缓冲区的作用就是,将接收到的报文暂时的保存起来,供上层读取。

2.5.UDP使用注意事项

需要注意的是,UDP协议报头当中UDP长度部分是16比特位的,也就是说UDP报文最大长度是Linux - 第18节 - 网络基础(传输层)的,因此一个UDP报文的最大长度是64K(包含UDP报头的大小)。

然而64K在当今的互联网环境下,是一个非常小的数字。如果需要传输的数据超过64K,就需要在应用层进行手动分包,多次发送,并在接收端进行手动拼装。

2.6.基于UDP的应用层协议

• NFS:网络文件系统。

• TFTP:简单文件传输协议。

• DHCP:动态主机配置协议。

• BOOTP:启动协议(用于无盘设备启动)。

• DNS:域名解析协议。

当然,也包括你自己写UDP程序时自定义的应用层协议。

3.TCP协议

TCP全称为“传输控制协议(Transmission Control Protocol)”,TCP协议是当今互联网当中使用最为广泛的传输层协议,没有之一。

TCP协议被广泛应用,其根本原因就是提供了详尽的可靠性保证,基于TCP的上层应用非常多,比如HTTP、HTTPS、FTP、SSH等,甚至MySQL底层使用的也是TCP。

3.1.可靠性

网络中通信存在不可靠性:

现代的计算机大部分都是基于冯诺依曼体系结构的。

Linux - 第18节 - 网络基础(传输层)

虽然这里的输入设备、输出设备、内存、CPU都在一台机器上,但这几个硬件设备是彼此独立的。如果它们之间要进行数据交互,就必须要想办法进行通信,因此这几个设备实际是用“线”连接起来的,其中连接内存和外设之间的“线”叫做IO总线,而连接内存和CPU之间的“线”叫做系统总线。由于这几个硬件设备都是在一台机器上的,因此这里传输数据的“线”是很短的,传输数据时出现错误的概率也非常低。

但如果要进行通信的各个设备相隔千里,那么连接各个设备的“线”就会变得非常长,传输数据时出现错误的概率也会大大增高,此时要保证传输到对端的数据无误,就必须引入可靠性。

总之,网络中存在不可靠的根本原因就是,长距离数据传输所用的“线”太长了,数据在长距离传输过程中就可能会出现各种各样的问题,而TCP就是在此背景下诞生的,TCP就是一种保证可靠性的协议。

UDP协议存在的意义:

TCP协议是一种可靠的传输协议,使用TCP协议能够在一定程度上保证数据传输时的可靠性,而UDP协议是一种不可靠的传输协议,那UDP协议这种不可靠的协议存在有什么意义呢?

不可靠和可靠是两个中性词,它们描述的都是协议的特点。

• TCP协议是可靠的协议,也就意味着TCP协议需要做更多的工作来保证传输数据的可靠,并且引起不可靠的因素越多,保证可靠的成本(时间+空间)就越高。
• 比如数据在传输过程中出现了丢包、乱序、检验和失败等,这些都是不可靠的情况。
• 由于TCP要想办法解决数据传输不可靠的问题,因此TCP使用起来一定比UDP复杂,并且维护成本特别高。
• UDP协议是不可靠的协议,也就意味着UDP协议不需要考虑数据传输时可能出现的问题,因此UDP无论是使用还是维护都足够简单。
• 需要注意的是,虽然TCP复杂,但TCP的效率不一定比UDP低,TCP当中不仅有保证可靠性的机制,还有保证传输效率的各种机制。
UDP和TCP没有谁最好,只有谁最合适,网络通信时具体采用TCP还是UDP完全取决于上层的应用场景。如果应用场景严格要求数据在传输过程中的可靠性,那么就必须采用TCP协议,如果应用场景允许数据传输出现少量丢包,那么肯定优先选择UDP协议,因为UDP协议足够简单。

3.2.TCP协议格式

TCP报头当中各个字段的含义如下:

• 源/目的端口号:表示数据是从哪个进程来,到发送到对端主机上的哪个进程。

• 
32位序号
/32
位确认号
:
后面详细讲解。

• 4位首部长度: 表示该TCP头部(报头:20字节标准长度+选项长度)有多少个32bit(有多少个4字节),所以TCP头部最大长度是15*4=60字节。
• 6位保留字段:TCP报头中暂时未使用的6个比特位。
• 16位窗口大小:保证TCP可靠性机制和效率提升机制的重要字段。
• 16位检验和:由发送端填充,采用CRC校验。接收端校验不通过,则认为接收到的数据有问题。(检验和包含TCP首部+TCP数据部分)
• 16位紧急指针:标识紧急数据在报文中的偏移量,需要配合标志字段当中的URG字段统一使用。
• 选项字段:TCP报头当中允许携带额外的选项字段,最多40字节。
TCP报头当中的6位标志位(6位保留字段):

• URG:紧急指针是否有效。
• ACK:确认序号是否有效。
• PSH:提示接收端应用程序立刻将TCP接收缓冲区当中的数据读走。
• RST:表示要求对方重新建立连接。我们把携带RST标识的报文称为复位报文段。
• SYN:表示请求与对方建立连接。我们把携带SYN标识的报文称为同步报文段。
• FIN:通知对方,本端要关闭了。我们把携带FIN标识的报文称为结束报文段。
TCP报头在内核当中本质就是一个位段类型,给数据封装TCP报头时,实际上就是用该位段类型定义一个变量,然后填充TCP报头当中的各个属性字段,最后将这个TCP报头拷贝到数据的首部,至此便完成了TCP报头的封装。

Linux - 第18节 - 网络基础(传输层)

注:TCP协议的报头包括前20个字节和选项部分,TCP协议的前20个字节是TCP协议报头的标准长度。

问题1:TCP如何将报头与有效载荷进行分离?

答:TCP协议报头的标准长度是20字节,但TCP协议报头其实还包括选项部分,绝大部分时间选项部分为空,那么报头就是标准的20字节,但还是有小概率选项部分不为空,报头就不是20字节。

当TCP从底层获取到一个报文后,虽然TCP不知道报头的具体长度,但报文的前20个字节是TCP的基本报头,并且这20字节当中涵盖了4位的首部长度。

因此TCP是这样分离报头与有效载荷的:

• 当TCP获取到一个报文后,首先读取报文的前20个字节,并从中提取出4位的首部长度,此时便获得了TCP报头的大小size。
• 如果size的值大于20字节,则需要继续从报文当中读取size−20字节的数据,这部分数据就是TCP报头当中的选项字段。
• 如果TCP报头当中不携带选项字段,那么TCP报头的长度就是20字节,此时报头当中的4位首部长度的值就为 20 ÷ 4 = 5 ,也就是0101。

问题2:TCP如何决定将有效载荷交付给上层的哪一个协议?

答:应用层的每一个网络进程都必须绑定一个端口号。

• 服务端进程必须显示绑定一个端口号。
• 客户端进程由系统动态绑定一个端口号。
而TCP的报头中涵盖了目的端口号,因此TCP可以提取出报头中的目的端口号,找到对应的应用层进程,进而将有效载荷交给对应的应用层进程进行处理。

发表回复