传输层
负责负责两台计算机之间的端到端的通信,确保数据能够可靠的传送到目标主机,为应用层提供可靠的数据传输服务。我们可以简单的理解为传输层协议是将数据直接发送到了网络当中。
端口号(Port)
标识了一个主机上进行通信的不同的应用程序。当主机从网络中获取数据时,数据需要自底向上进行交付,而上层存在多个应用程序,那么交付给哪一个应用程序就由端口号来决定。
因此端口号是传输层的概念,在传输层协议的报头中包含有与端口号相关的字段。
五元组标识一个通信
在TCP/IP协议中, 用 "源IP", "源端口号", "目的IP", "目的端口号", "协议号
" 这样一个五元组来标识一个通信(可以通过 netstat -n
查看)。其中 IP 地址和端口号标识网络中唯一的一个进程,而协议号是一个整数,用于标识传输层使用的协议类型。常见的传输层协议包括 TCP 协议、UDP 协议等。
通过
netstat
命令 查看五元组信息
-a
:显示所有网络连接,包括正在监听的和建立的连接。-n
:以数值形式显示网络地址和端口号,而不是域名和服务名。-p
:显示与连接相关的进程和程序名。-r
:显示当前系统的路由表信息。-s
:显示网络统计信息,如 TCP 和 UDP 的错误、丢包等。-t
:显示所有 TCP 连接。-u
:显示所有 UDP 连接。-l
:显示处于监听状态的连接。关于协议号和端口号
- 协议号是存在于IP报头当中的,其长度是8位,协议号指明了数据报所携带的数据是使用何种协议,以便于让目的主机的IP层知道应该将该数据交付给传输层的哪一个协议进行处理。
- 端口号是存在于UDP和TCP报头当中的,其长度是16位,端口号的作用是唯一标识一台主机上的某个进程。
端口号的长度是16位,因此端口号的范围是 0~65535
0 - 1023
: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.1024 - 65535
: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的
有些服务器是非常常用的, 为了使用方便, 人们约定一些常用的服务器, 都是用以下这些固定的端口号:
ssh
服务器, 使用22端口ftp
服务器, 使用21端口telnet
服务器, 使用23端口http
服务器, 使用80端口https
服务器, 使用443端口
查看知名端口号
/etc/services # 在该文件中查看网络服务名和它们对应使用的端口号及协议
我们自己写一个程序使用端口号时, 要避开这些知名端口号。
一个端口号可以被多个进程绑定吗?
- 通常情况下,一个端口号只能被一个进程绑定。这是因为端口号用于标识网络上的特定服务,当客户端尝试连接到服务器的某个端口时,操作系统需要知道哪个进程负责处理该连接请求。如果多个进程绑定到同一个端口号,操作系统将无法确定应该将连接请求发送给哪个进程。因此,当一个进程试图绑定到已经被另一个进程占用的端口时,操作系统通常会返回错误。
- 但是如果采取的通信协议不同,就可以绑定同一个端口号。例如:一个进程可以使用 TCP 协议绑定到端口号 80,而另一个进程可以使用 UDP 协议绑定到端口号 80。这是因为操作系统不仅根据端口号,还根据通信协议来区分不同的服务。因此,当客户端尝试连接到服务器的某个端口时,操作系统会根据客户端使用的通信协议来确定应该将连接请求发送给哪个进程。
一个进程是否可以绑定多个端口号?
一个进程可以绑定多个端口号,只不过现在这多个端口号唯一标识的是同一个进程罢了。我们限制的是从端口号到进程的唯一性,而没有要求从进程到端口号也必须满足唯一性,因此一个进程是可以绑定多个端口号的。
netstat
是一个用来查看网络状态的重要工具
- 语法: netstat [选项]
- 功能:查看网络状态
- 常用选项:
n
拒绝显示别名,能显示数字的全部转化成数字l
仅列出有在 Listen (监听) 的服務状态p
显示建立相关链接的程序名t
(tcp)仅显示tcp相关选项u
(udp)仅显示udp相关选项a
(all)显示所有选项,默认不显示LISTEN相关
pidof
命令用于查找指定名称进程的进程 ID。
例如,我们随便创建一个进程演示一下:
pidof
命令可以配合kill
命令快速杀死一个进程。
我们一般在应用层看到的端口号大部分都是16位的,根本原因就是传输层协议当中的端口号就是16位。
UDP是如何将报头和有效载荷进行分离的?
UDP报头当中包含4个字段,每个字段的长度都是16位,总共8字节。因此UDP采用的实际上是一种定长报头。UDP在读取报文时读取完前8个字节后剩下的就是有效载荷了。
UDP如何决定将有效载荷交付给上层的哪一个协议的?
应用层的每一个网络进程都会绑定一个端口号,服务端进程必须显示绑定一个端口号,UDP就是通过报头当中的目的端口号来找到对应的应用层进程的。
这里我们需要注意的是:操作系统会维护一个端口号到进程的映射表,当网络协议栈确定了数据报应该交给哪个端口号时,它会查询这张映射表,找到对应的进程,然后将有效载荷传递给该进程进行处理。
如何理解报头?
Linux内核是C语言写的,UDP协议又属于内核协议栈,因此UDP协议也是用C语言写的,UDP报头本质上也是一个结构体或者位段类型的。
UDP数据封装
- 当应用层将数据交给传输层后,在传输层就会创建一个UDP报头类型的变量,然后填充报头当中的各个字段,此时就得到了一个UDP报头。
- 此时操作系统再在内核中开辟一块空间将UDP报头和有效载荷拷贝到一起,此时就形成了UDP报文。
UDP数据分用
- 当传输层从下层获取到一个报文后,就会读取该报文的前8个字节,提取出对应的目的端口号。
- 通过目的端口号找到对应的上层应用进程,然后将剩下的有效载荷向上囧付给该应用进程。
应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并。这就叫做 面向数据报
。
这意味着它将应用层传递给它的数据作为一个独立的数据报来处理。每个数据报都包含了足够的信息,使得接收端能够将其独立地传递给上层应用程序。
用UDP传输100个字节的数据,如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的一次recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节。
sendto、recvfrom、send、recv、write 和 read 等
IO 类接口都是用于在应用程序和操作系统内核之间传输数据的系统调用。它们的本质是在用户空间和内核空间之间拷贝数据。
除了拷贝数据之外,这些 IO 类接口还会执行其他操作,如检查套接字状态、设置套接字选项、处理错误等。因此,它们不仅仅是简单的拷贝函数。
全双工
UDP协议使用缓冲区来存储发送和接收的数据。在Linux内核中,这些缓冲区由sk_buff结构体表示。每个sk_buff结构体都包含指向数据缓冲区的指针,以及有关数据包的其他信息。
当应用程序调用sendto函数发送数据时,内核会将数据拷贝到一个sk buff结构体中,然后将其添加到发送队列中。网络协议栈会从发送队列中取出数据包,并将其发送到网络。
当内核接收到一个UDP数据包时,它会将数据包存储在一个sk buff结构体中,并将其添加到接收队列中。当应用程序调用recvfrom函数接收数据时,内核会从接收队列中取出一个数据包,并将其中的数据拷贝到应用程序提供的缓冲区中。
UDP本身会维护一个接收缓冲区
,当有新的UDP报文到来时就会把这个报文放到接收缓冲区当中,此时上层在读数据的时就直接从这个接收缓冲区当中进行读取就行了,而如果UDP接收缓冲区当中没有数据那上层在读取时就会被阻塞。因此UDP的接收缓冲区的作用就是,将接收到的报文暂时的保存起来,供上层读取。
我们注意到,UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。
然而64K在当今的互联网环境下, 是一个非常小的数字,如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装。
NFS
: 网络文件系统TFTP
: 简单文件传输协议DHCP
: 动态主机配置协议BOOTP
: 启动协议(用于无盘设备启动)DNS
: 域名解析协议当然,也包括我们自己写UDP程序时自定义的应用层协议。