零、传输层的作用是负责数据能够从发送端传输到接收端
一、再来认识一下端口号
端口号(Port)标识了一个主机进行通信的不同的应用程序。在TCP/IP协议中,用"源IP","源端口号","目的IP","目的端口号","协议号"这样一个五元组来标识一个通信(可以通过netstat -n查看)。
bash
netstat -n
端口号范围划分:
- 0 - 1023:知名端口号,HTTP,FTP,SSH等这些广为使用的应用层协议,他们的端口号都是固定的。
- 1024 - 65535:操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围随机分配的。
认识知名端口号:
有些服务器是非常有用的,为了方便使用,人们约定一些常用的服务器,都是用以下这些固定的端口号:
- ssh服务器:使用22端口号
- ftp服务器:使用21端口
- telnet服务器:使用23端口号
- http服务器:使用80端口号
- https服务器:使用443端口号
bash
cat /etc/services // 使用该命令可以看到知名端口号
下面来思考两个问题:
一个进程是否可以bind多个端口号?
可以的,因为在一个进程中,我们可以使用TCP套接字,也可以使用UDP套接字,两个套接字可以分别bind在同一个进程中。
一个端口号是否可以被多个进程bind?不可以,因为不满足一个端口号对应一个进程的原则。
端口号在操作系统中如何理解?
在操作系统内部会维护一个哈希表用于存储进程和端口号的对应关系。在进程中,通过bind可以将端口号与进程插入到哈希表中。
二、UDP协议
2.1 UDP协议的格式(报头长度固定,为20字节)
UDP格式还是比较简单的,相对比于TCP协议的格式。报头部分只有四个部分:16位源端口号,16位目的端口号,16位UDP长度,16位UDP校验和;有效载荷部分是数据(如果有)。
- 16位源端口号:表示这个数据报是由谁发送的;
- 16位目的端口号:表示这个数据发送给谁;
- 16位UDP长度:表示整个数据报(UDP首部 + UDP数据)的最大长度;
- 如果校验和出错,就会直接丢弃。
2.2 UDP的特点
UDP传输的过程类似于寄信。UDP是无连接,不可靠,面向数据报的协议。
- 无连接:知道对端的IP和端口号就直接进行传输,不需要建立连接;
- 不可靠:没有确认机制,没有重传机制;如果因为网络故障,使得该数据报无法发送到对方,UDP协议层也不会给应用层返回任何错误信息;
- 面向数据报:不能够灵活的控制读写数据的次数和数量。
2.3 面向数据报
在应用层学习套接字的时候,我们知道UDP发送数据是直接发送数据报,没有粘包问题;而TCP发送数据是直接发送字节流,有粘包问题。
应用层交给UDP多长的报文,UDP原样发送,即不会拆分,也不会合并。
举个例子:使用UDP传输100个字节的数据:如果发送端调用一次sendto,发送100个字节,那么接收端也必须调用对应的一次recvfrom,接收100个字节;而不能循环调用10次recvfrom,每次接收10个字节。
2.4 UDP的缓冲区
- UDP没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核将数据传给网络层协议进行后续的传输动作
- UDP具有接收缓冲区,但是这个接受缓冲区不能保证收到的UDP数据报的顺序和发送UDP数据报的顺序一致;如果缓冲区满了,再次到达的UDP数据报就会被丢弃
UDP的socket即能读,也能写,这个概念就叫做全双工。
2.5 UDP使用注意事项
我们注意到UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64K(包含UDP首部)。然而64K在当今的互联网下是一个非常小的数字。如果我们需要传输的数据超过64K,就需要再应用层中手动分包,多次发送,并在接收端手动拼装。
但是在这里,UDP的数据报也不能太大,因为在数据链路层中需要限制每次传输数据的大小,所以,在这里UDP的数据报也可能受到该影响。
2.6 基于UDP的应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
三、操作系统管理UDP
在网络基础中,我们学习了操作系统,硬件,用户和网络协议栈的联系,发现网络协议栈中的TCP层和IP层是在操作系统内核中实现的。下面,我们来看一看在代码层面上实现的UDP协议:
3.1 UDP报文的代码实现
协议是通信双方约定好的数据结构,我们可以根据这个数据结构填充信息,然后发送给对方,对方也可以根据该数据结构将信息提取出来进行处理。在上面我们已经了解UDP报文的格式,所以在代码层面中,我们会很容易写出结构体:
cpp
#include <linux/types.h>
struct udphdr
{
__be16 source;
__be16 dest;
__be16 len;
__sum16 check;
}
3.2 UDP报文如何封装和解包
在操作系统中,在网络协议中,可以同时存在很多个已经收到的报文,但是这写报文还没有进行处理;甚至拷贝到传输层的接受缓冲区;甚至没有交给传输层而是在网络层和数据链路层中存在。因此,操作系统需要将这些报文进行管理起来,需要先描述,再组织。怎么组织呢???
我们可以使用链表将他们组织起来,在struct sk_buffer结构体中,我们需要使用两个指针来指向协议中报头的位置和正文的位置,还有一个指针是用来形成链表的。我们可以通过指针的操作进行存储报头和分解报头。