目录
前言:
传输层主要负责数据能够从发送端传输接收端,在socket编程中我们写的套接字就是传输层中的协议
常见的传输层协议有两个:
- UDP协议
- TCP协议
这两个协议中有相同的特征,如下:
- UDP和TCP都需要通过IP地址和端口号标识互联网中的一个进程
- UDP和TCP在进行数据传输时,它们都是全双工的
注意:全双工指的是通信过程中数据可以在两个方向上同时传输,即实现信号的双向同时传输(A→B且B→A)
支持全双工的原因是内核的传输层中维护了两个缓冲区,发送缓冲区和接收缓冲区。这两个缓冲区互不影响!这一点在应用层中详细说明过
- 图中,主机A从发送缓冲区中拿取数据发送给主机B的同时,主机B也可以从自己的发送缓冲区中拿取数据发送给主机A,这两个步骤是不会相互影响的!
端口号与进程的关系
一个进程可以bind多个端口号
- 实际上,我们需要的是客户端可以使用ip地址和端口号唯一标识一个进程,而反过来成不成立都可以
一个端口号不可以被多个进程bind
实际上,我们要使用UDP/TCP时server都需要手动进行bind
一般地、内核中维护了一个端口号和PCB之间的kv结构,所以我们bind的时候是把这个端口号和进程PCB之间构建好映射以后,插入到哈希表之类的数据结构中!就能很快的根据端口号找到对应进程
UDP协议
UDP协议的特征
- 无连接
- 不可靠
- 面向数据报
无连接主要说明UDP的通信就像是寄信一样,你写好了一封信以后不需要经过收信人的同意可以直接送到他家去。UDP在发送数据之前不需要建立连接。发送方可以随时随地发送数据,接收方也无需事先同意接收。这种特性使得UDP的通信过程更加简单、高效,但同时也意味着UDP不提供像TCP那样的可靠传输机制。
网络传输的过程中是可能会丢包的,即传输过去的数据对方接收不到,如果是使用UDP进行传输,那么发生丢包什么都不管,这也是说明UDP是不可靠的原因!不可靠仅仅是UDP的特征,不是它有BUG。正因为它不需要保证数据的可靠,所以它才能简单、高效。TCP是可靠的,但TCP的可靠是付出了很多代价的
应用层交给UDP多长的报文, UDP原样发送, 既不会拆分, 也不会合并。假设用UDP传输100个字节的数据,如果发送端调用一次sendto, 发送100个字节, 那么接收端也必须调用对应的 一次 recvfrom, 接收100个字节; 而不能循环调用10次recvfrom, 每次接收10个字节。总得来说,面向字节流比起面向数据报更加灵活一些
UDP协议端格式
- 图中除了数据以外的所有字段,它们共同组成了UDP通信时的传输层报头
- 数据实际上就是有效载荷
- UDP是可以进行双向通信的,它的传输层报头中包含了源端口和目的端口,这样就可以确定把数据交到哪个应用层进程
实际上,所谓的协议,也就是一个双方都定义出来的结构化的数据,在内核中UDP的协议的伪代码如下:
struct XXX
{
uint16_t src_port;//源端口
uint16_t dst_port;//目的端口
uint16_t udp_len;//UDP长度
uint16_t check;//校验和
};
- 这个结构体被定义在通信双方OS内核中。协议就是结构体,这就是大多数协议的本质,不仅仅UDP
UDP中有两个字段是相对陌生的:
- 16位UDP长度
- 16位UDP校验和
尽管UDP本身是一个无连接的、不可靠的传输协议,但校验和机制为数据传输提供了一定程度的可靠性保障。通过检测并丢弃可能损坏的数据报,校验和有助于减少上层协议接收到错误数据的可能性。
UDP长度表示的是报头+有效载荷的长度,其中报头的大小是固定的8字节
UDP长度 = 有效载荷长度 + 8
正因为UDP长度的存在,提供给UDP面向数据报的能力
UDP的缓冲区
- 发送缓冲区存在的意义是发送方可以进行等待接收方,当接收方速度过慢时,先把数据放入发送缓冲区作为缓冲
- 由于UDP不需要保证数据可靠性,所以对于UDP来说不需要内核中的发送缓冲区,因为我根本不需要临时保存数据,不需要管对方是啥状态!sendto直接拷贝数据给OS即可
- UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃;
分离与交付(封装与解包)
UDP的分离:实际上根据UDP长度即可得到有效载荷的长度,就能把UDP报头进行封装/分离
UDP的交付:根据目的端口即可知道交付给哪个进程
系统角度理解封装和解包
- 实际上,报文的传输并不是互斥的,即主机A向主机B发送报文时可能还存在主机C也同时在发送报文给主机B。
- OS内可以同时存在很多个收到的报文,这个报文还没来得及处理,这个报文可能只到了接收缓冲区甚至是网络/链路层
- 由于OS内可能存在大量的还没来得及处理的报文,所以OS需要对这些大量的报文进行管理
- OS对报文的管理方式就是先描述、后组织。
所以OS内一定存在着一个描述报文的内核结构体,这个结构体叫做sk_buffer。并且也一定会给报文提供内存空间!
示例图:
- 图中,对报文的管理就变为了对链表的增删查改
封装:所谓的封装就是给报文添加上对应层的报头
- 所谓的封装报头就是把sk_buffer中的head指针左移,左移的字节数要看封装的是什么报头。不管封装的是什么报头,它都是结构体类型,都能sizeof算出它的大小
- 通过head指针填完对应报头的信息
解包:所谓的解包就是对报文去掉对应层的报头
- 所谓的解包就是sk_buffer中的head指针右移,右移的字节数要看封装的是什么报头。
- 指针右移完以后交付给上层就完成了一层解包
所以所谓的封装和解包,在内核中只需要进行指针移动即可控制
UDP的注意事项
在UDP报头中,UDP长度是用16位的一个整形来存储的,换句话来说,你发送的数据的字节数不能超过这个整形能表示的字节数,这个整形能表示64KB。若你一次发送的数据超过64KB,那么就需要在发送方应用层手动分包进行分批发送!分批发送完以后接收方再把收到的数据手动拼接起来