传输层
是整个网络体系结构中的关键层次之一,主要负责向两个主机中进程之间的通信提供服务。由于一个主机同时运行多个进程,因此运输层具有有复用和分用功能。传输层在终端用户之间提供透明的数据传输,向上层提供可靠的数据传输服务。传输层在给定的链路上通过流量控制、分段/重组和差错控制来保证数据传输的可靠性。传输层的一些协议是面向链接的,这就意味着传输层能保持对分段的跟踪,并且重传那些失败的分段。
传输层负责端对端的传输,不论是TCP协议还是UDP协议都是和端口有着联系
端口的划分:uint16_t (0~2^16)
http:80
https:443
ssh服务:22
ftp服务:21
Telnet服务:23
mysql:3306
oracle:1521
知名端口的查看
cat /etc/services
无连接,不可靠,面向数据报
无连接
:知道端口的ip和端口号就可以直接进行传输,不需要建立连接
不可靠
:没有确认应答和重传机制。如果数据在传输的过程中发生丢失,UDP协议也不会给应用层返回任何错误的信息
面向数据报
:对于信息只能整条发送与整条接收,不会存在两条数据并存在缓冲区中。也就不存在粘包问题
网络抓包
sudo tcpdump -i any port 19999 -s 0 -w 1.dat
然后在Windows下的wireshark
中打开后:
可以看到只有数据发送的过程,没有连接
数据长度
uint16_t:最大的数值是65536,也就是说UDP协议单次传输的数据最大长度时65536个字节
UDP协议在传输数据的时候,不会出现粘包
问题的,因为他的收发缓冲区一次只能存在一条数据
当应用层一次需要传输的数据大于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
sendto
接口将数据提交到传输层的UDP发送缓冲区中,然后打上UDP协议的报头,就可以直接交给网络层进行下一步传输了recvfrom
接口将数据从传输层的接收缓冲区中拷贝到应用层,UDP接收缓冲区不保证数据的有序到达,也不保证可靠性;EMSGSIZE
错误UDP的应用
DNS,域名解析协议。将域名转换为IP地址的时候,使用UDP协议
面向连接,可靠的传输,面向字节流
面向连接
:使用TCP协议进行通信的双方在通信之前必须建立连接,然后才可以进行通信。TCP连接是一个全双工的,也就是通信双方可以互相进行发送和接收数据。(三次握手)
可靠的传输
:TCP协议有一个确认应答与超时重传的机制,当数据在传输的过程中丢失的话,对端就不会进行确认,等到一个时间段后发送端就会重新发送这个数据。
面向字节流
:发送端与接收端进行读写数据之间没有任何数量关系,发送端多次待发送的数据可以一起放在发送缓冲区中,接收端可以一下全部接受
32位序号/32位确认号
:确认应答机制
4位TCP报头长度
:表示TCP的头部有多少个32位数据(四个字节),所以TCP头部的最大长度时 15 * 4 = 60
6个标志位
16位窗口大小
:两个字节数据,窗口表示的范围0~2^16
16为校验和
:检验数据是否传输完成
16位紧急指针
:和URG标志位一起来使用,如果URG标志位为1,则紧急指针指向的数据有效
40字节头部选项
:MSS–》最大报文段长度
TCP连接的通信双方,在通信之前进行三次握手的请求连接,通信完毕之后进行四次挥手关闭连接
有一个概念就是客户端与服务端是相对的,而不是绝对的。我们认为的时候,率先发起连接的一方称为客户端,被动连接的一方称为服务端。
三次握手的前提就是连接方与被动连接方都完成了前期的准备工作
接下来就是三次握手的过程,这个过程就是一个确认应答的阶段
通信双方通信,那么肯定就有通信结束的时候。又因为TCP协议是面向连接的,所以在结束的时候不能一句话不说,悄悄就走了,这让对方情何以堪?
四次挥手,肯定也是四个阶段,因为可能是服务端先给客户端发起断开请求,也可能是客户端先给服务端发起断开请求。
所以发起请求的一方称为主动断开连接的一方,另一方就是对端,也就是被动断开连接的一方
到了这里,其实四次挥手还不算完,为什么呢?这个时候主动断开连接方还需要考虑,自己发送给对端的ACK报文,对方有没有收到?
MSL,最大报文段生存时间,指的是发送方认为TCP报文在网络中最大的生存时间,一般是60s
cat /proc/sys/net/ipv4/tcp_fin_timeout
等待两个MSL,就是为了防止发送方给对端发送的ACK确认退出报文,在途中因为各种原因丢失的情况。
这个时候给了发送方一个重新发送的机会,假设ACK报文发生了丢失
这个时候,如果第二次的ACK报文没有发生丢失,对端在收到ACK报文后,就进入了CLOSED状态。发送方在第二个MSL结束后就会进入CLOSED状态。
而如果又发生了丢失。。。。。这时发送方和对端因为2MSL已经结束了,就进入了CLOSED状态
2MSL == 丢失ACK的MSL + 重传FIN的MSL
如果一个TCP协议的程序,他的主动断开连接的一方是服务端,那么在服务端断开连接之后,立刻重新连接的时候,就会发现该端口处于bind error: Address already in use
的状态,如下所示:
问题的根源:
就是因为我们直接退出了服务端,使得服务端所占有的端口没有变成CLOSE
状态,而是TIME_WAIT
的状态
我们的服务端率先断开了连接,在连接断开的时候,就有一个TIME_WAIT
的状态,他需要等待2MSL的时间。
这个行为是传输层TCP的行为,即使我们的应用程序已经退出掉了,但是内核中对应的19999端口还是被占用的
同理,当我们的客户端端口后,客户端的进程也属于一个TIME_WAIT
的状态,但是进程号是随机的。。。我们服务端的端口号是固定的,所以客户端先断开连接后影响比较小。
解决地址复用的函数接口
#include
#include
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
SOL_SOCKET:通用套接字选项 --》地址复用
IPPROTO_TCP:TCP选项
IPPROTO_IP:IP选项
SOL_SOCKET -->
SO_REUSERADDR:允许重用本地地址和端口
SO_RECVBUF:获取接收缓冲区的大小
IPPROTO_TCP -->
TCP_MAXSEG:获取TCP最大数据段的大小
IPPROTO_IP -->
IP_TTL:获取最大字节数,也就是最大生存空间
int i = 1;
&i;