传输层(一)UDP协议与TCP协议

传输层是整个网络体系结构中的关键层次之一,主要负责向两个主机中进程之间的通信提供服务。由于一个主机同时运行多个进程,因此运输层具有有复用和分用功能。传输层在终端用户之间提供透明的数据传输,向上层提供可靠的数据传输服务。传输层在给定的链路上通过流量控制、分段/重组和差错控制来保证数据传输的可靠性。传输层的一些协议是面向链接的,这就意味着传输层能保持对分段的跟踪,并且重传那些失败的分段。

传输层负责端对端的传输,不论是TCP协议还是UDP协议都是和端口有着联系

端口号

端口的划分:uint16_t (0~2^16)

  • 知名端口

http:80
https:443
ssh服务:22
ftp服务:21
Telnet服务:23

  • 非知名端口

mysql:3306
oracle:1521

知名端口的查看

cat /etc/services

传输层UDP协议

  • 特点

无连接,不可靠,面向数据报

无连接:知道端口的ip和端口号就可以直接进行传输,不需要建立连接
不可靠:没有确认应答和重传机制。如果数据在传输的过程中发生丢失,UDP协议也不会给应用层返回任何错误的信息
面向数据报:对于信息只能整条发送与整条接收,不会存在两条数据并存在缓冲区中。也就不存在粘包问题

  • UDP协议的报头

源端口(2字节)+ 目的端口(2字节)+ 数据长度(最大可表示的字节数2^16 – 65536)+ 校验和
在这里插入图片描述

网络抓包

  • 抓包的命令
sudo tcpdump -i any port 19999 -s 0 -w 1.dat

然后在Windows下的wireshark中打开后:
在这里插入图片描述
可以看到只有数据发送的过程,没有连接

数据长度

uint16_t:最大的数值是65536,也就是说UDP协议单次传输的数据最大长度时65536个字节
UDP协议在传输数据的时候,不会出现粘包问题的,因为他的收发缓冲区一次只能存在一条数据

  • 一次传输的数据小于65536时,选择直接传送就可以了
  • 当一次传输的数据大于65536时,就需要在应用层进行拆分,向数据分为小于65536的几部分,然后按照顺序依次发送给对端即可

当应用层一次需要传输的数据大于2^16时,我们就需要在应用层进行分片传输。就是说在应用层的时候,就将大于2 ^16的数据进行拆分,分多次使用UDP协议进行传输
在这里插入图片描述
因为UDP协议的特性是面向数据报,数据都是整条整条的传输,所以在拆分后认为应用层传输的每一条数据都是一个完整的UDP数据报

所以说,对于接受端接受UDP数据的进程而言,从协议栈中的传输层中的UDP接受的数据可能并不是一个完整的数据。为了解决数据传输不完整的问题,数据发送双方需要在应用层的时候就定制自定义协议,标识着应用层数据的长度和判断数据是否完整
在这里插入图片描述

校验和

因为UDP协议在传输数据的时候可能存在丢失的情况,所以就用校验和来判断UDP数据在传输的过程中是否发生损坏

  • 发生损坏,直接丢弃数据报,不会传递给应用层
  • 没有损坏,在应用层调用recvfrom的时候,将数据报提交给应用层

校验和的计算

将UDP的数据报分成多个16位的数据,除了检验和不进行相加外,其他数据进行加运算
伪头部(源IP + 目的IP + UDP的协议号(17) + 数据长度)+ UDP头部(源端口 + 目的端口 + 数据长度) + 所有需要发送的数据

在这里插入图片描述
在计算的时候,因为都是16位的数据进行相加,所有很容易出现溢出的情况,这个时候就需要进行回卷

因为两个16位的数据进行相加,最多只能溢出一位,就变成了17位数据。那么回卷就是把这个17位的数据分成最高位 + 低16位两部分,再把这两部分进行相加,这就是新的结果
在这里插入图片描述
在计算校验和的时候,就是把所有加起来的结果进行反码运算,最后这个反码运算的结果就是16位的校验和。

所以说,校验和 和 其他所有数据相加的结果就是 FFFF

UDP缓冲区

  1. 缓冲区中对应应用层的数据,都是整条发送与整条接收的
  2. 对于发送而言,应用层先使用sendto接口将数据提交到传输层的UDP发送缓冲区中,然后打上UDP协议的报头,就可以直接交给网络层进行下一步传输了
  3. 对于接收而言,应用层使用recvfrom接口将数据从传输层的接收缓冲区中拷贝到应用层,UDP接收缓冲区不保证数据的有序到达,也不保证可靠性;
  4. 当接收缓冲区满的时候,从网卡中接收的UDP数据报就会被丢弃,然后内核会返回一个EMSGSIZE错误

UDP的应用

DNS,域名解析协议。将域名转换为IP地址的时候,使用UDP协议

TCP协议

  • 特点

面向连接,可靠的传输,面向字节流

面向连接:使用TCP协议进行通信的双方在通信之前必须建立连接,然后才可以进行通信。TCP连接是一个全双工的,也就是通信双方可以互相进行发送和接收数据。(三次握手)

可靠的传输:TCP协议有一个确认应答与超时重传的机制,当数据在传输的过程中丢失的话,对端就不会进行确认,等到一个时间段后发送端就会重新发送这个数据。

面向字节流:发送端与接收端进行读写数据之间没有任何数量关系,发送端多次待发送的数据可以一起放在发送缓冲区中,接收端可以一下全部接受

在这里插入图片描述

  • TCP协议段格式

在这里插入图片描述
源/目的端口号:表示数据从哪里来,到哪里去

32位序号/32位确认号:确认应答机制

4位TCP报头长度:表示TCP的头部有多少个32位数据(四个字节),所以TCP头部的最大长度时 15 * 4 = 60

6个标志位

  • URG:紧急指针是否有效
  • ACK:确认号是否有效
  • PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
  • RST:对方要求重新建立连接,携带RST标识的称为复位报文段
  • SYN:请求建立连接,携带SYN标识的称为同步报文段
  • FIN:通知对方,这个端口要关闭了,称携带FIN标识的称为结束报文

16位窗口大小:两个字节数据,窗口表示的范围0~2^16

16为校验和:检验数据是否传输完成

16位紧急指针:和URG标志位一起来使用,如果URG标志位为1,则紧急指针指向的数据有效

40字节头部选项:MSS–》最大报文段长度

网络抓包

在这里插入图片描述
TCP连接的通信双方,在通信之前进行三次握手的请求连接,通信完毕之后进行四次挥手关闭连接

三次握手

有一个概念就是客户端与服务端是相对的,而不是绝对的。我们认为的时候,率先发起连接的一方称为客户端,被动连接的一方称为服务端。

三次握手的前提就是连接方与被动连接方都完成了前期的准备工作
在这里插入图片描述
接下来就是三次握手的过程,这个过程就是一个确认应答的阶段

  1. 客户端率先发起连接请求,向服务端发送SYN报文
  2. 服务端在收到SYN报文后,向客户端回复ACK + SYN报文。其中,ACK报文是告诉客户端:服务端收到了你发送的信息,至于是发送的哪一个请求,就是后面跟着的SYN报文
  3. 客户端收到了服务端发送的确认收到报文的请求,并回复服务端自己收到的请求,发送一个ACK报文

在这里插入图片描述

四次挥手

通信双方通信,那么肯定就有通信结束的时候。又因为TCP协议是面向连接的,所以在结束的时候不能一句话不说,悄悄就走了,这让对方情何以堪?

四次挥手,肯定也是四个阶段,因为可能是服务端先给客户端发起断开请求,也可能是客户端先给服务端发起断开请求。

所以发起请求的一方称为主动断开连接的一方,另一方就是对端,也就是被动断开连接的一方

  1. 主动断开连接的一方,率先给对端发送一个FIN报文,表示自己不想聊了,要退出了
  2. 对端在收到FIN报文后,向主动断开连接的一方发送ACK报文,表名自己收到了你想要退出的消息
  3. 紧接着对端向主动断开连接的一方也发送一个FIN报文,既然你不想聊了,我也不想聊了,表示自己也要退出了
  4. 主动断开连接方先收到对端的ACK报文,这个时候还不能退出,因为不确定对端是否还有话要说。
    然后收到了对端的FIN报文,知道了对端也要退出,就给对端发送一个ACK确认的报文,告诉对端,我知道你退出了

在这里插入图片描述
到了这里,其实四次挥手还不算完,为什么呢?这个时候主动断开连接方还需要考虑,自己发送给对端的ACK报文,对方有没有收到?
在这里插入图片描述

MSL,最大报文段生存时间,指的是发送方认为TCP报文在网络中最大的生存时间,一般是60s

  • 查看MSL的命令
cat /proc/sys/net/ipv4/tcp_fin_timeout

在这里插入图片描述
等待两个MSL,就是为了防止发送方给对端发送的ACK确认退出报文,在途中因为各种原因丢失的情况。

这个时候给了发送方一个重新发送的机会,假设ACK报文发生了丢失

  1. 第一个MSL,这时对端因为还没有收到发送方的ACK报文,自己还处于LASK_ACK的状态,于是就再次给发送方发送一个FIN退出的报文,告诉自己要走了
  2. 第二个MSL,这时发送方又收到了对方的FIN报文,知道自己刚才那个ACK报文丢失了,就再给对方发一个ACK报文,好让对方退出

这个时候,如果第二次的ACK报文没有发生丢失,对端在收到ACK报文后,就进入了CLOSED状态。发送方在第二个MSL结束后就会进入CLOSED状态。

而如果又发生了丢失。。。。。这时发送方和对端因为2MSL已经结束了,就进入了CLOSED状态

2MSL == 丢失ACK的MSL + 重传FIN的MSL

由2MSL引起的地址复用问题

如果一个TCP协议的程序,他的主动断开连接的一方是服务端,那么在服务端断开连接之后,立刻重新连接的时候,就会发现该端口处于bind error: Address already in use的状态,如下所示:
在这里插入图片描述
问题的根源:
就是因为我们直接退出了服务端,使得服务端所占有的端口没有变成CLOSE状态,而是TIME_WAIT的状态

我们的服务端率先断开了连接,在连接断开的时候,就有一个TIME_WAIT的状态,他需要等待2MSL的时间。

这个行为是传输层TCP的行为,即使我们的应用程序已经退出掉了,但是内核中对应的19999端口还是被占用的

同理,当我们的客户端端口后,客户端的进程也属于一个TIME_WAIT的状态,但是进程号是随机的。。。我们服务端的端口号是固定的,所以客户端先断开连接后影响比较小。
在这里插入图片描述
解决地址复用的函数接口

#include <sys/types.h>          
#include <sys/socket.h>

int setsockopt(int sockfd, int level, int optname,
       const void *optval, socklen_t optlen);
  • sockfd:将要被设置的套接字
  • level:指定套接字的层次,他的取值有三种
SOL_SOCKET:通用套接字选项 --》地址复用
IPPROTO_TCP:TCP选项
IPPROTO_IP:IP选项
  • optname:在level中要完成的任务
SOL_SOCKET -->
	SO_REUSERADDR:允许重用本地地址和端口
	SO_RECVBUF:获取接收缓冲区的大小
IPPROTO_TCP -->
	TCP_MAXSEG:获取TCP最大数据段的大小
IPPROTO_IP -->
	IP_TTL:获取最大字节数,也就是最大生存空间
  • optval:需要完成的任务,地址复用–》传入1。传入的是一个 变量的地址
int i = 1;
&i;
  • len:optval的长度

然后在创建套接字的同时,加上地址复用的函数
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章