我们以前在学习HTTP等应用层协议时,为了便于理解,简单的认为HTTP协议是将请求和响应直接发送到了网络当中。但实际进行网络传输时数据要从应用层先将数据交给传输层,由传输层对数据做进一步处理后再将数据继续向下进行交付,该过程贯穿整个网络协议栈,最终才能将数据发送到网络当中。
传输层负责可靠性传输,确保数据能够可靠地传送到目标地址。为了方便理解,在学习传输层协议时也可以简单的认为传输层协议是将数据直接发送到了网络当中。
端口号(Port)标识一个主机上进行网络通信的不同的应用程序。当主机从网络中获取到数据后,需要自底向上进行数据的交付,而这个数据最终应该交给上层的哪个应用处理程序,就是由该数据当中的目的端口号来决定的。
因此端口号是属于传输层的概念的,在传输层协议的报头当中就会包含与端口相关的字段。
在TCP/IP协议中, 用 “源IP”, “源端口号”, “目的IP”, “目的端口号”, “协议号” 这样一个五元组来标识一个通信(可以通过netstat -n查看);
协议号与端口号的对比:
- 协议号是存在于IP报头当中的,其长度是8位。
- 协议号指明了数据报所携带的数据是使用的何种协议,以便让目的主机的IP层知道应该将该数据交付给传输层的哪个协议进行处理。
- 端口号是存在于UDP和TCP报头当中的,其长度是16位。端口号的作用是唯一标识一台主机上的某个进程。
- 协议号是作用于传输层和网络层之间的,而端口号是作用于应用层于传输层之间的。
端口号的范围划分
端口号的长度是16位,因此端口号的范围是0 ~ 65535:
认识知名端口号
有些服务是非常常用的, 为了使用方便, 人们约定一些常用的端口号,以下这些固定的端口号:
服务名称 | 端口号 |
---|---|
ssh服务 | 22 |
ftp服务 | 21 |
telnet服务 | 23 |
http服务 | 80 |
https服务 | 443 |
执行下面的命令, 可以看到知名端口号
cat /etc/services
我们自己写一个程序使用端口号时, 要避开这些知名端口号
iostat
命令:该命令主要用于输出磁盘IO和CPU的统计信息。当然你可能没有此命令,那么就需要通过软件包管理器来安装,需要注意的是,不是直接 install iostat 而是 install sysstat, iostat 也是 sysstat 的一部分,所以我们安装要安装sysstat。
其常见的选项如下:
CPU属性值说明:
pidof
:该命令可以通过进程名,查看进程id。例如,我们用pidof命令查看ssh进程。
UDP(UserDatagramProtocol)是一个简单的面向数据报的传输层协议,UDP是属于内核当中的,是操作系统本身协议栈自带的,其代码不是由上层用户编写的,UDP的所有功能都是由操作系统完成,因此网络也是操作系统的一部分。
主要内容:
我们以前在应用层看到的端口号大部分都是16位的,其根本原因就是因为传输层协议当中的端口号就是16位的。
解包和分用对于任何一个协议来说都是它们要面临的首要任务。
UDP的解包:
从UDP的格式的首部信息中我们能够知道UDP的报头信息是固定长度的!总长度为8字节,所以我们收到一个UDP报文时,可以先读取前8个字节得到UDP报头,然后再根据报头中的相关信息,来对数据进行分用。
UDP的分用:
应用层的每一个网络进程都会绑定一个端口号。UDP就是通过报头当中的目的端口号来找到对应的应用层进程,然后对数据向上交付完成分用。
ps: Linux
内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号得到对应的进程ID,进而找到对应的应用层进程。
如何理解UDP的报头?
操作系统是C语言写的,而UDP协议又是属于内核协议栈的,因此UDP协议也一定是用C语言编写的,UDP报头又是一个结构化的数据,所以UDP报头就是一个struct
结构体!
使用C语言实现UDP的报头一般有两种方式,一种是普通结构体,一种是结构体位段。
// 普通结构体
struct udp_header
{
uint16_t src_port; // 源端口号
uint16_t dst_port; // 目的端口号
uint16_t udp_len; // udp的长度
uint16_t udp_chk; // udp校验和
};
// 结构体位段
struct udp_header
{
uint32_t src_port : 16; // 源端口号
uint32_t dst_por : 16; // 目的端口号
uint32_t udp_len : 16; // udp的长度
uint32_t udp_chk : 16; // udp校验和
};
UDP数据封装:
UDP传输的过程类似于寄信,其特点如下:
- 面向数据报的理解:
应用层交给UDP多长的数据, UDP加上报头以后原样发送,既不会拆分, 也不会合并。
例如用UDP传输100个字节的数据:
如果发送端调用一次sendto
, 发送100个字节,那么接收端也必须调用对应的一次recvfrom
, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节;
UDP的缓冲区
sendto
会直接将数据报交给内核, 由内核将数据交给网络层,然后由网络层进行后续的传输动作。socket
既能读, 也能写, 这个概念叫做全双工。为什么UDP要有接收缓冲区?
如果UDP没有接收缓冲区,那么就要求上层及时将UDP获取到的报文读取上去,如果一个报文在UDP没有被读取,那么此时UDP从底层获取上来的报文数据就会被迫丢弃。
一个报文从一台主机传输到另一台主机,在传输过程中会消耗主机资源和网络资源。如果UDP收到一个报文后仅仅因为上次收到的报文没有被上层读取,而被迫丢弃一个可能并没有错误的报文,这就是在浪费主机资源和网络资源。
因此UDP本身是会维护一个接收缓冲区的,当有新的UDP报文到来时就会把这个报文放到接收缓冲区当中,此时上层在读数据的时就直接从这个接收缓冲区当中进行读取就行了,而如果UDP接收缓冲区当中没有数据那上层在读取时就会被阻塞。因此UDP的接收缓冲区的作用就是,将接收到的报文暂时的保存起来,供上层读取。
需要注意的是,UDP协议报头当中的UDP最大长度是16位的,因此一个UDP报文的最大长度是: 2 16 b y t e = 64 K B 2^{16} byte = 64KB 216byte=64KB (包含UDP报头的大小)。
然而64K在当今的互联网环境下,是一个非常小的数字。如果需要传输的数据超过64K,就需要在应用层进行手动分包,多次发送,并在接收端进行手动拼装。
当然,也包括你自己写UDP程序时自定义的应用层协议。