一.传输层协议地位
在上一节中,我们提到到了HTTP,HTTPS等应用层协议,现在我们就要进一步向下挖掘,来到我们的传输层协议
假如说应用层负责传递的是数据的内容,专注于为⽤户提供应⽤功能;那传输层协议负责的就是传输的策略问题,这就好比我们运送快递,把东西交给顺丰就够了,至于怎么发,什么时候发,并不是我上层应用层关心的问题!运送快递的时间,方式(策略)是由我们的传输层负责的.
在传输层中会有两个主要协议
一个称作UDP协议,简单到只负责发送数据包,不保证数据包是否能抵达到对方那,但它有一个最大的特点,就是简单!传输效率⾼,这在我们之前用套接字编写的时候已经体会过了(PS:UDP协议也可以实现可靠传送,只需要按照TCP特性,对它进行上层封装即可,当然假如要用UDP实现商用,这就不是一件简单的事情了,早期的QQ实际上就是采用UDP协议进行传输数据)
另一个称作TCP协议,英文名为Transmission Control Protocol,⼤部分应⽤现在使⽤的都是TCP 传输层协议,它最大的特点就是能保证数据包能可靠地传输给对方,当然代码编写难度也相对增大许多,这在我们之前编写时也已经了解到
我们本章谈的主要还是UDP协议,进一步了解它的特性和设计细节
二.再谈端口号
2.1 Port号
我们之前提到过,当设备作为接收方时,传输层则要负责把数据包传给对应的应用,但是⼀台设备上可能会有很多应⽤在接收或者传输数据(很多个进程),总不能你使用QQ发送数据,最后发送的语音信号到了对方的微信中去吧
因此需要用⼀个编号将应⽤区分开来,这个编号就是我们的端口号,它用于标识了一个主机上进行通信的不同的应用程序
当然,这里还是把我们之前所疑惑的地方再啰嗦一遍
Q1.
每一个进程不是都有对应的进程pid吗?用pid进行标识不也可以吗?为什么要单独再设计一个端口号的概念呢?
1.不是所有的进程都要进行网络通信,假如用PID来标识,就很难进行区分,这个进程到底要不要进行网络通信呢?
2.解耦,进程PID还牵连到进程管理,我们希望的是进程管理,与网络两者可以区分开来,自成一套体系
Lg.这就好像我们每个人都有自己的身份证号,用来标识我们的唯一性,但我们在学校也有自己的学号!它也可以用来标识我们在学校的唯一性,对学号的处理,并不会改变我们的身份证号,学校管理和国家进行行政管理,这是两套不同的管理体系,互不干扰。
Q2.
一个端口号可以被多个进程绑定吗?
不允许!!!
那就没有区分不同进程这一说法了
一个进程可以关联多个端口号吗?
可以!!!
这主要是能够实现多路复用的功能,通过关联多个端口号,一个进程可以同时监听或处理多个网络连接。这种多路复用的机制允许一个进程同时与多个客户端通信,而无需为每个连接创建一个独立的进程
Q3.
常见端口号划分
(1)0 - 1023: 知名端口号, HTTP, FTP, SSH等这些广为使用的应用层协议, 他们的端口号都是固定的.
ssh服务器, 使用22端口
ftp服务器, 使用21端口
telnet服务器, 使用23端口
http服务器, 使用80端口
https服务器, 使用443端口
我们可以使用下面的指令查看对应的端口号是否被占用,然后自己在编写对应程序的时候,要避开这些知名端口号
cat /etc/services
1024 - 65535:
操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的,前面我们也提到过了,这主要是防止我们用户端的应用程序端口号大家设计一样,导致用户比如打开百度浏览器,就不能再打开QQ的尴尬情况
2.2 netstat指令
在TCP/IP协议中,我们用源ip,源端口号,目的ip,目的端口号,协议号,五元组来标识一个通信
具体过程如下:
1.先提取出数据当中的目的IP地址和目的端口号,确定该数据是发送给当前服务进程的。
2.提取出数据当中的协议号,为该数据提供对应类型的服务.
(协议号和端口号不同!协议号是存在于IP报头当中的,长度是8位,作用是指明数据报所携带的数据是使用的何种协议;而端口号是存在于TCP或UDP报头当中,作用就是我们提到过的,标识网络中的唯一进程)
3.提取出数据当中的源IP地址和源端口号,将其作为响应数据的目的IP地址和目的端口号,将响应结果发送给对应的客户端进程
在linux系统下,我们可以用这样的指令进行查看对应不同进程的网络状态
netstat -n
常见选项主要有下面四个
n(number) 能显示数字的就显示成数字
t 只查tcp
u 只查udp
p 进程pid
l 只查看listen监听情况
一般我们都习惯四个选项都用上,不然输出的进程网络状态太多了
当然这里还有个注意点,假如你想要将所有的进程网络状态都看到,还需要切换到root,才有对应的权限
我们切换为root后,就没有上面那句提示了
2.3 iostat指令
iostat指令主要用于输出磁盘IO和CPU的统计信息
常见的选项有下面几种
c:显示CPU的使用情况。
d:显示磁盘的使用情况。
N:显示磁盘列阵(LVM)信息。
n:显示NFS使用情况。
k:以KB为单位显示。
m:以M为单位显示。
t:报告每秒向终端读取和写入的字符数和CPU的信息。
V:显示版本信息。
x:显示详细信息。
p:显示磁盘分区的情况。
假如在Linux系统中尝试使用iostat命令,但系统提示没有这个指令,那么可能是没有安装sysstat包
还需要用指令安装sysstat包
sudo yum install sysstat
一般我们使用的时候,带个-x选项即可查看IO设备和CPU的详细使用情况
2.4 Pidof指令
通过pidof指令,我们可以轻松通过进程名, 就能查看对应的进程id,配合kill指令,能够轻松杀死对应的进程
pidof [进程名]
在编写TCP网络服务器时,我们曾经提到过守护进程的概念
守护进程是个特殊的孤儿进程,脱离终端而存在,能够避免进程被任何终端所产生的信息所打断,并且它执行过程中的信息也不在任何终端上显示
守护进程的名字,一般以d结尾
和终端脱离关系,自己在运行,我们会想到什么呢?
有时候有一个终端一直死循环打印,但我们依旧可以验证用户,创建新的终端(子进程),这个程序不就是守护进程吗?
我们可以调用下面的指令查看
ps axj | head -1 && ps axj | grep sshd
可以看到,无论是我们的root还是zzq用户,都是这个守护进程的子进程
输入我们刚学习的指令pidof,验证一下,是否真的能通过进程名,拿到对应的进程PID
三.UDP协议
3.1 协议端格式
16位源端口号:表示数据从哪里来。
16位目的端口号:表示数据要到哪里去。
16位UDP长度:表示整个数据报(UDP首部+UDP数据)的长度。
16位UDP检验和:如果UDP报文的检验和出错,就会直接将报文丢弃。
数据:就是我们上层应用层交付下来的,诸如HTTP请求等等
无论学习哪个协议,最关键的就是两个问题,如何分离与向上交付
第一.报头和有效载荷是如何分离的? 不然我们无法做到向上交付
当然,对于UDP协议来说,这个问题比较简单
我们可以发现,报头它是固定八个字节的,只要读取前八个字节,就能获取到报头,剩下的就是我们的数据
第二.有效载荷是如何向上做到交付的呢?
我们说了对面服务端可能有多个进程,每个进程至少含有一个端口Port号,有效载荷(数据)怎么知道要发给谁呢?
答案前面我们也已经揭晓了,UDP协议格式中16位目的端口号在那摆着呢!我们通过目的端口号就能找到对应的应用层进程
3.2 封装与解包
那现在我们就要进一步思考一个问题了,到底什么叫做封装呢?什么又叫做解包呢?
你直接给我展示出UDP协议端格式,让人摸不着头脑的晕
我们知道tcp/Ip是属于操作系统的,即它的实现其实并不是我们用户做,大佬已经帮我们写好了,我们使用的是他们写好的接口而已
而Linux系统我们知道,是用C语言写的
所以不难得出一个结论,UDP是用C语言实现的
那C语言中涉及管理不同类型数据的结构是什么呢?
答案就是结构体!
所以报头(协议)的本质就是一个结构化数据!(Struct结构体)
我们的UDP报头实际就是一个位段类型
我们所谓的封装
第一步,就是接收相应从用户层传下的数据
第二步,往结构体里面对应位置填充对应的数据,得到UDP报头
第三步,定义更大的缓冲区,往里面填有效载荷,将UDP报头和有效载荷拷贝到一起,此时就形成了UDP报文
在C语言中,先用char* 指针p指向结构体最开始,然后强转为对应的struct udp_header* 指针,往里面填数据即可
那什么是解包呢?
对于我们的结构体来说,它的报头,有效字段是已经定义好的
从里面依次取数据,就是我们的解包,根本没有我们想象中那么复杂
start指针+sizeof报头字节大小,就能取出我们的有效载荷
由于协议标准规定,所以UDP协议跨越平台!不需要像我们的应用层一样考虑序列化,反序列化等等问题
3.3 特点
3.3.1 面向数据报
应用层交给udp多长的报文,udp原样进行发送,既不会拆分,也不会进行合并,发多少,收多少,这被我们称作面向数据报.
比如用UDP传输100个字节的数据:
如果发送端调用一次sendto,发送100字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节
3.3.2 缓冲区
UDP没有真正意义上的发送缓冲区,一旦调用类似sendto的系统接口,就会直接把数据进行封装,然后由内核交付给下层网络层发送
但它是具有接收缓冲区的,原因就在于假如没有接收缓冲区,对面没有接收到数据而直接选择丢弃可能正确的报文,综合考量下来,效率底下
因此,当有新的UDP报文到来时就会把这个报文放到接收缓冲区当中,此时上层在读数据的时就直接从这个接收缓冲区当中进行读取就行了,而如果UDP接收缓冲区当中没有数据那上层在读取时就会被阻塞
需要注意的是,接收缓冲区不能保证接收到的数据顺序,和我们发送出去的数据顺序是一致的,这也是UDP相对TCP没有那么可靠的体现之一
假如接收缓冲区满了,此时才会把对面发过来的数据丢弃,单个报文最多64KB,然而64K在当今的互联网环境下, 是一个非常小的数字.
所以,如果我们需要传输的数据超过64K, 就需要在应用层手动的分包, 多次发送, 并在接收端手动拼装
3.4 基于UDP协议的应用层协议
NFS: 网络文件系统
TFTP: 简单文件传输协议
DHCP: 动态主机配置协议
BOOTP: 启动协议(用于无盘设备启动)
DNS: 域名解析协议