目录
1、传输层
在学习HTTP等应用层协议时,为了便于理解,可以简单的认为HTTP协议是将请求和响应直接发送到了网络当中。但实际应用层需要先将数据交给传输层,由传输层对数据做进一步处理后再将数据继续向下进行交付,该过程贯穿整个网络协议栈,最终才能将数据发送到网络当中。
- 传输层负责可靠性传输,确保数据能够可靠地传送到目标地址。为了方便理解,在学习传输层协议时也可以简单的认为传输层协议是将数据直接发送到了网络当中。
传输层协议有两种:UDP协议和TCP协议,在具体展开讲解之前,先再来谈谈端口号。
再谈端口号
端口号(port)的作用实际就是标识一台主机上的一个进程。当主机从网络中获取到数据后,需要自底向上进行数据的交付,而这个数据最终应该交给上层的哪个应用处理程序,就是由该数据当中的目的端口号来决定的。
- 从网络中获取的数据在进行向上交付时,在传输层就会提取出该数据对应的目的端口号,进而确定该数据应该交付给当前主机上的哪一个服务进程。
在TCP/IP协议中,用“源IP地址”,“源端口号”,“目的IP地址”,“目的端口号”,“协议号”这样一个五元组来标识一个通信。(可以通过netstat -n查看);
- 如下有客户端A和B,它们都要访问同一个服务器。客户端A有两个画面,对于服务器,它的第一个挑战就是我应该把响应是给A还是B,这取决于是谁在请求;第二个挑战就是服务器要把信息推送到同一台主机上的不同画面(进程),服务端就可以根据客户端的源IP地址来区分是谁发起的请求。即使客户端A有两个画面,但是这两个画面的端口号是不一样的,服务器在推送的时候根据IP把信息推送到客户端A,再根据不同的端口号把信息推送到不同的画面上。
总的来说这台服务器就是通过“源IP地址”,“源端口号”,“目的IP地址”,“目的端口号”,“协议号”这5个数字来识别一个通信的。
- 先提取出数据当中的目的IP地址和目的端口号,确定该数据是发送给当前服务进程的。
- 然后提取出数据当中的协议号,为该数据提供对应类型的服务。
- 最后提取出数据当中的源IP地址和源端口号,将其作为响应数据的目的IP地址和目的端口号,将响应结果发送给对应的客户端进程。
协议号 vs 端口号:
- 协议号是存在于IP报头当中的,其长度是8位。协议号指明了数据报所携带的数据是使用的何种协议,以便让目的主机的IP层知道应该将该数据交付给传输层的哪个协议进行处理。
- 端口号是存在于UDP和TCP报头当中的,其长度是16位。端口号的作用是唯一标识一台主机上的某个进程。
- 协议号是作用于传输层和网络层之间的,而端口号是作用于应用层于传输层之间的。
端口号范围划分
一共有 2^16 个端口
- 0 ~ 1023:知名端口号。比如HTTP,FTP,SSH等这些广为使用的应用层协议,它们的端口号都是固定的。
- 1024 ~ 65535:操作系统动态分配的端口号。客户端程序的端口号就是由操作系统从这个范围分配的。
认识知名端口号
有些服务器是非常常用的,为了使用方便,人们约定一些常用的服务器,都是用以下这些固定的端口号:
- ssh服务器,使用22端口。
- ftp服务器,使用21端口。
- telnet服务器,使用23端口。
- http服务器,使用80端口。
- https服务器,使用443端口。
我们可以执行此命令 , 查看知名端口号:
vim /etc/services
- 我们自己写一个程序使用端口号时,要避开这些知名端口号。
问1:一个端口号是否可以被多个进程绑定?
- 不能!因为端口号的作用就是唯一标识一个进程,如果绑定一个已经被绑定的端口号,就会出现绑定失败的问题。
问2:一个进程是否可以绑定多个端口号?
- 可以!这与“端口号必须唯一标识一个进程”是不冲突的,只不过现在这多个端口唯一标识的是同一个进程罢了。
- 我们限制的是从端口号到进程的唯一性,而没有要求从进程到端口号也必须满足唯一性,因此一个进程是可以绑定多个端口号的。
netstat 与 pidof
netstat命令
netstat是一个用来查看网络状态的重要工具。
- 语法:netstat [选项]
- 功能:查看网络状态
其常见的选项如下:
- n:拒绝显示别名,能显示数字的全部转换成数字。
- l:仅列出处于LISTEN(监听)状态的服务。
- p:显示建立相关链接的程序名。
- t(tcp):仅显示tcp相关的选项。
- u(udp):仅显示udp相关的选项。
- a(all):显示所有的选项,默认不显示LISTEN相关。
示例1: n 拒绝显示别名,能显示数字的全部转化成数字
示例2: l 仅列出有在 Listen (监听) 的服务状态
示例3: p 显示建立相关链接的程序名
- 普通用户无法查看到建立相关链接的PID和程序名,只有root权限才能查看到
pidof命令
- pidof命令可以通过进程名,查看进程id。
比如我们要查看sshd的pid,可以使用如下命令:
ps axj | head -1 && ps ajx | grep sshd
如果指向查看sshd的pid,不想查看其它的,就可以直接使用pidof命令:
2、UDP协议
网络套接字编程时用到的各种接口,是位于应用层和传输层之间的一层系统调用接口,这些接口是系统提供的,我们可以通过这些接口搭建上层应用,比如HTTP。我们经常说HTTP是基于TCP的,实际就是因为HTTP在TCP套接字编程上搭建的。
- 而socket接口往下的传输层实际就是由操作系统管理的,因此UDP是属于内核当中的,是操作系统本身协议栈自带的,其代码不是由上层用户编写的,UDP的所有功能都是由操作系统完成,因此网络也是操作系统的一部分。
UDP协议端格式
0~31意味着报文的宽度是0~31的,UDP报文中的前8个字节(4个字段)称为UDP报头,剩下的数据就是有效载荷。
- 16位源端口号:表示数据从哪里来。
- 16位目的端口号:表示数据要到哪里去。
- 16位UDP长度:表示整个数据报(UDP首部+UDP数据)的长度。
- 16位UDP检验和:如果UDP报文的检验和出错,就会直接将报文丢弃。
我们在应用层看到的端口号大部分都是16位的,其根本原因就是因为传输层协议当中的端口号就是16位的。
问1:UDP采用的定长报头,那么UDP是如何进行封装和解包的呢?
- 当上层应用调用sendto告诉我要发送的hello,传输层在添加UDP报头的时候其实就是在前面写上8字节数据即可。
- 因为UDP采用定长报文,所以对方在收到完整的报文后,它只需要从报文中提取前8个字节,即报头,剩下的就是有效载荷。
问2:UDP如何决定将有效载荷交付给上层的哪一个协议?
- UDP上层也有很多应用层协议,因此UDP必须想办法将有效载荷交给对应的上层协议,也就是交给应用层对应的进程。
- 应用层的每一个网络进程都会绑定一个端口号,服务端进程必须显示绑定一个端口号,客户端进程则是由系统动态绑定的一个端口号。UDP就是通过报头当中的目的端口号来找到对应的应用层进程的。
- 注意:内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号得到对应的进程ID,进而找到对应的应用层进程。
问3:如何理解报头?
操作系统是用C语言写的,而UDP协议又是属于内核协议栈的,因此UDP协议也一定是用C语言编写的,UDP报头实际就是一个位段类型。例如:
添加报头的本质,其实就是拷贝对象:
- 把对象的成员变量填好,通过sizeof(udp_header)的大小拷贝进二进制,再强转成udp_header(udp_hdr)类型。
UDP的特点
UDP传输的过程就类似于寄信,其特点如下:
- 无连接:知道对端的IP和端口号就直接进行数据传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报:不能够灵活的控制读写数据的次数和数量。
注意:报文在网络中进行路由转发时,并不是每一个报文选择的路由路径都是一样的,因此报文发送的顺序和接收的顺序可能是不同的。
面向数据报
数据报:报文和报文之间有明显边界,因为有16位UDP长度。
- 应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并;
举例:用UDP传输100个字节的数据:
- 如果发送端调用一次sendto,发送100字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节;而不能循环调用10次recvfrom,每次接收10个字节。
UDP的缓冲区
- UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。
- UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃。
- UDP的socket既能读,也能写,因此UDP是全双工的。
UDP使用注意事项
需要注意的是,UDP协议报头当中的UDP最大长度是16位的,因此一个UDP报文的最大长度是64K(包含UDP报头的大小)。
- 然而64K在当今的互联网环境下,是一个非常小的数字。如果需要传输的数据超过64K,就需要在应用层进行手动分包,多次发送,并在接收端进行手动拼装。
基于UDP的应用层协议
- NFS:网络文件系统。
- TFTP:简单文件传输协议。
- DHCP:动态主机配置协议。
- BOOTP:启动协议(用于无盘设备启动)。
- DNS:域名解析协议。
当然,也包括你自己写UDP程序时自定义的应用层协议。