目录
- 传输层
- 再谈端口号
- 端口号范围划分
- 认识知名端口号
- UDP协议
- UDP协议格式
- UDP数据封装
- UDP数据分用
- UDP协议的特点
- 面向数据报
- UDP缓冲区
- UDP使用注意事项
- 基于UDP的应用层协议
传输层
实际上我们应用层的数据并不是直接发给网络的,而是需要先将数据发送给传输层,传输层进行进一步处理再讲数据向下交付,该过程贯穿整个网络协议栈,最终才能将数据发送到网络当中。
再谈端口号
端口号(Port)标识一个主机上进行网络通信的不同的应用程序。当主机从网络中获取到数据后,需要自底向上进行数据的交付,而这个数据最终应该交给上层的哪个应用处理程序,就是由该数据当中的目的端口号来决定的。
在TCP/IP协议中,用“源IP地址”,“源端口号”,“目的IP地址”,“目的端口号”,“协议号”这样一个五元组来标识一个通信。
比如有多台客户端主机同时访问服务器,这些客户端主机上可能有一个客户端进程,也可能有多个客户端进程,它们都在访问同一台服务器。
- 先提取出数据当中的目的IP地址和目的端口号,确定该数据是发送给当前服务进程的。
- 然后提取出数据当中的协议号,为该数据提供对应类型的服务。
- 最后提取出数据当中的源IP地址和源端口号,将其作为响应数据的目的IP地址和目的端口号,将响应结果发送给对应的客户端进程。
通过netstat
命令可以查看到这样的五元组信息。
语法:netstat [选项]
功能:查看网络状态
常用选项:
- n 拒绝显示别名,能显示数字的全部转化成数字;
- l 仅列出有在 Listen (监听) 的服务状态;
- p 显示建立相关链接的程序名;
- t (tcp)仅显示tcp相关选项;
- u (udp)仅显示udp相关选项;
- a (all)显示所有选项,默认不显示LISTEN相关。
其中Proto表示协议类型,Local Adderss表示源IP地址和源端口号,Foreign Adderss表示目的IP地址和目的端口号。
端口号范围划分
- 0 - 1023: 知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他们的端口号都是固定的。
- 1024 ~ 65535:操作系统动态分配的端口号。客户端程序的端口号就是由操作系统从这个范围分配的。
认识知名端口号
有些服务器是非常常用的,这些服务器的端口号一般都是固定的:
- ssh服务器,使用22端口。
- ftp服务器,使用21端口。
- telnet服务器,使用23端口。
- http服务器,使用80端口。
- https服务器,使用443端口。
一个进程是否可以bind多个端口号?
一个端口号绝对不能被多个进程绑定,因为端口号的作用就是唯一标识一个进程,如果绑定一个已经被绑定的端口号,就会出现绑定失败的问题。
一个进程是否可以绑定多个端口号?
一个进程是可以绑定多个端口号的,这与“端口号必须唯一标识一个进程”是不冲突的,只不过现在这多个端口唯一标识的是同一个进程罢了。
我们限制的是从端口号到进程的唯一性,而没有要求从进程到端口号也必须满足唯一性,因此一个进程是可以绑定多个端口号的。
pidof命令
在查看服务器的进程id时非常方便:
语法:pidof [进程名]
功能:通过进程名, 查看进程id。
pidof命令可以配合kill命令快速杀死一个进程。
UDP协议
UDP协议格式
UDP协议格式如下:
- 16位源端口号:表示数据从哪里来。
- 16位目的端口号:表示数据要到哪里去。
- 16位UDP长度:表示整个数据报(UDP首部+UDP数据)的长度。
- 16位UDP检验和:如果UDP报文的检验和出错,就会直接将报文丢弃。
我们在应用层看到的端口号大部分都是16位的,其根本原因就是因为传输层协议当中的端口号就是16位的。
对于任何协议,几乎都要解决两个问题:
- 如何分离(封装);
- 如何交付。
如何分离?
我们所说的分离其实就是将报头与有效荷载进行分离,UDP的报头含有四个字段,每个字段的长度是16为,共8个字节。因为报头就是固定的8个字节,UDP在读取完8个字节长度以后剩下的就是有效荷载了。
如何交付?
UDP需要将有效荷载交付给上层对应的协议,也就是交给响应的进程。应用层中的每一个网络进程都会绑定一个端口,对于客户端进程来说,端口是操作系统动态进行分配的,而服务端就需要我们显示的绑定端口,UDP就是通过报头当中的目的端口号来找到对应的应用层进程的。因为内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号得到对应的进程ID,进而找到对应的应用层进程。
理解报头
操作系统是C语言写的,而UDP协议又是属于内核协议栈的,因此UDP协议也一定是用C语言编写的,UDP报头实际就是一个位段类型。
UDP数据封装
- 当应用层数据交付给传输层以后,在传输层就会创建一个UDP报头类型的变量,然后填充报头当中的各个字段,此时就得到了一个UDP报头。
- 此时操作系统再在内核当中开辟一块空间,将UDP报头和有效载荷拷贝到一起,此时就形成了UDP报文。
UDP数据分用
当传输层从下层获取到一个报文以后,就会读取出前8个字节,提取出对应的目的端口号,通过目的端口号找到对应上层应用层进程,然后将剩下的有效载荷向上交付给该应用层进程。
UDP协议的特点
UDP传输的过程就类似于寄信,其特点如下:
- 无连接:知道对端的IP和端口号就直接进行数据传输,不需要建立连接。
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障该段无法发到对方,UDP协议层也不会给应用层返回任何错误信息。
- 面向数据报:不能够灵活的控制读写数据的次数和数量。
面向数据报
应用层交给UDP多长的报文,UDP就原样发送,既不会拆分,也不会合并,这就叫做面向数据报。
比如用UDP传输100个字节的数据:
- 如果发送端调用一次sendto,发送100字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节;而不能循环调用10次recvfrom,每次接收10个字节。
UDP缓冲区
- UDP没有真正意义上的发送缓冲区。调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作。
- UDP具有接收缓冲区。但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致;如果缓冲区满了,再到达的UDP数据就会被丢弃。
- UDP的socket既能读,也能写,因此UDP是全双工的。
其实我们平时所说的read/write/recv/send/recvfrom/sendto等诸多IO类接口并不是直接在网络中进行数据收发的,本质上他们属于拷贝函数,将数据拷贝到对应的接收和发送缓冲区中,然后由相应的缓冲区进行数据交互。
为什么UDP要有接收缓冲区?
如果UDP中不存在接收缓冲区,上层在接收数据是就需要及时的将UDP读取到的报文给读取上去,否则就会出现上一个报文还没有被读取,下一个报文已经来了,此时刚从底层获取的报文就会被丢弃。我们在传输过程中是会消耗资源的,如果出现因为报文未被及时读取而被丢弃的问题,就是在浪费资源。
因此UDP本身是会维护一个接收缓冲区的,当有新的UDP报文到来时就会把这个报文放到接收缓冲区当中,此时上层在读数据的时就直接从这个接收缓冲区当中进行读取就行了,而如果UDP接收缓冲区当中没有数据那上层在读取时就会被阻塞。因此UDP的接收缓冲区的作用就是,将接收到的报文暂时的保存起来,供上层读取。
UDP使用注意事项
需要注意的是,UDP协议报头当中的UDP最大长度是16位的,因此一个UDP报文的最大长度是64K(包含UDP报头的大小)。
然而64K在当今的互联网环境下,是一个非常小的数字。如果需要传输的数据超过64K,就需要在应用层进行手动分包,多次发送,并在接收端进行手动拼装。
基于UDP的应用层协议
- NFS:网络文件系统。
- TFTP:简单文件传输协议。
- DHCP:动态主机配置协议。
- BOOTP:启动协议(用于无盘设备启动)。
- DNS:域名解析协议。
当然,也包括你自己写UDP程序时自定义的应用层协议。