Linux---TCP/IP协议栈中应用层与传输层常见协议--实现与特性

1. 应用层

应用层主要用于负责应用程序之间的数据沟通
应用层的协议一般有用户自己制定, 但也不缺乏一些有大佬制定出来,被人们广泛认可的知名协议

私有协议—网络版计算器

假如要实现一个网络版计算器
客户端 : 将两个数字和一个运算符号传给服务端
服务端 : 服务端对接受到的数据进行解析,运算,之后将运算的结果返回给客户端
假设有约定 : 我们将两个数字与一个运算符用一个结构体保存起来,发送数据时将结构体按照一定的规则转化为字符串 ; 接收到数据时,在将字符串转换为结构体这就是一个私有协议
其中我们把结构体转换为字符串的过程称为序列化,反过来的过程称为反序列化 : (序列化:将数据对象按照指定的协议在内存中进行排布, 排布成为可持久化存储或者可数据传输的数据串)

http协议—知名协议

http协议称为—超文本传输协议: 他早期用于超文本文件的传输,在传输层基于TCP实现应用层协议, 并且明文传输 https协议是在http协议的基础上添加了一层加密

  • URL
    在了解http协议之前我们先了解URL , URL就是我们常说的网址也叫统一资源定位符
    Linux---TCP/IP协议栈中应用层与传输层常见协议--实现与特性_第1张图片
    在一个完整的URL中一共包含了7各组成部分 如上图所示,他们由特定的符号隔开

协议名称 : 注释使用了应用层的哪个协议

登录信息 : 一般为用户登录的账号与密码

服务器地址 : 表示服务器的IP地址 , IP地址可以用域名来代替

服务器端口 : http协议默认使用80端口

带层次的文件路径 : 请求访问的文件路径

查询字符串 : 一个个(key==value的键值对), 是客户提交给服务器的数据, 提交的数据中不能出现特殊字符串, 容易与URL中的分隔符造成二义,造成url解析失败,因此再出现特殊字符的时候要进行转译
C++ 这个字符为例, 当我们在百度中输入C++时,他被解析成了c%2B%2B
在这里插入图片描述
其中+号为一个特殊自字符, 按照URL编码的规则对+号做了转译
URL编码 : 特殊字符ASCII码值所对用的16进制数表示的字符串, ‘+’ 号对应的ASCII值为43,对应的16进制数位0x2B,因此 ‘+’ 通过URL编码被转移成了2B ,并且为了表示这个符号是经过转译得到的数据,所以在前面加了 符号 ‘%’ , 在解析时在查询字符串中遇到%,则认为紧跟其后的两个字符需要进行解码, 第一个字符转换为数字左移4为(乘以16)+第二个字符转换为数字

片段标识符 :

  • http协议格式

首行 : 主要描述当前协议的版本以及请求的资源路径 / 请求方法 / 状态响应
头部 : 更加细致的描述本次数据的一些详细信息
空行 : 用来间隔头部与正文
正文 : 具体传输给对端的数据

  • 各个字段

首行请求 : 格式为-----请求方法 + URL + 协议版本\r\n
请求的方法一共有9种 常用的有GET / HEAD / POST / PUT / DELETE
GET : 主要用于服务器请求实体资源, 也可以他提交数据, 提交的数据作为查询字符串存储在URL中(URL的长度是有限的 1kb----4kb)
POST : 主要用于向服务器提交数据表单, 提交的数据存放在正文中
HEAD : 类似以GET,但是只用于获取响应头部

头部 : 一个个 key: val 形式组成的键值对, 并且每一个键值对都以 \r\n 结尾

空行 : 因为我们的客户端在发送请求的时候是可以并行发送的,可以一次性发送多条请求. 那么在服务端处理请求的时候,多条请求重叠在一起的时候, 此时当服务端那数据拿到连着两个\r\n\r\n的时候,就可以确定,接下来的部分是正文.

响应首行 : 格式为-----协议版本 状态响应码 状态描述符\r\n
状态响应码有五种 : 1xx/2xx/3xx/4xx/5xx
2xx : 表示请求已经正确处理 典型----(202 OK)
3xx : 表示资源请求重定向 典型(301----永久重定向 , 302----临时重定向 )
4xx : 客户端错误 典型(404----请求的资源不存在 , 400----请求的格式有问题)
5xx : 服务端错误 典型(502-----网关错误)
注意:
资源的重定向指的是 , 当前请求得资源不再目前的文件路径中, 而服务端会自动给用户转向新的路径
服务端一般不会出错, 一般出现5xx的状态码地时候是代理网关的错误 , 客户端将请求发送到网关上,网关在向服务端去请求,若此过程中出错,则会出现5xx

  • 常见的头部协议字段
    Content-Lentgh : 表示正文的长度
    Content- Type : 表示了正文的数据类型
    Transfer-Enconding : 表示按块接受数据
    Location : 重定向的目标地址
    注意 :
    按块接受数据表示,当我们请求的资源过去庞大时,(例如数据库中的文件), 服务端会按照一块一块的方式将数据库中的文件拿到网页上, 并且在拿之前会描述只一次拿了几个字节

Cookie和Session及两者的联系
Cookie : 服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态.
Session : 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息.
联系 : 由于http协议是无状态的协议 , 所以浏览器并不知道是哪个用户在和服务器打交道, 这就需要一个机制来告诉服务器. 而Cookie和Session就是完成这个任务的.
当用户第一次在浏览器上登录时 , 服务器根据用户的信息就会在服务端创建一个Session, 并且将Sessoin的唯一表示SessionID返回给浏览器, 浏览器接受待SessionID之后, 就会将其保存在Cookie中,同时 Cookie 记录此 SessionID 属于哪个域名
当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作

Linux---TCP/IP协议栈中应用层与传输层常见协议--实现与特性_第2张图片

传输层

传输层的主要作用是负责端与端之间的数据传输
传输层得主要协议是UDP协议与TCP协议

UDP协议:用户数据报协议

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

UDP协议的协议字段一共有四个部分 : 16位源端口, 16位目的端口, 16位数据包长度 , 16位校验和

  1. 16位源端口与16位目的端口描述了数据从流向

  2. 16位校验和 : 判断接受的数据与发送的数据是否一致, 利用二进制反码求和算法进行判断
    二进制反码求和算法 :一开始的数据报中的校验和位置是没有数据的, 当数据头部封装好之后, 整个数据报从第0个字节开始,进行取反相加,一直都最后一个字节, 由于校验和的长度只有16位(两个字节),因此当相加的数超出两个字节时, 就从第16位截断, 将16位之后的高位在进行相加, 如此直到所有字节相加完, 相加完成之后将求和所得到的数据放入到16位校验和的位置 ; 当接收到数据的时候, 由于校验和的位置有数据, 再将整个数据报从第一个字节取反码求和后,所得的数据刚好是 0 , 这就说明发送与接受的数据报是完整的.

  3. 16位数据报长度 : 描述数据报的大小(包含头部)
    由于数据报的长度只有16位, 因此一个数据报的最大长度只能有64K. 由于报头是UDP协议进行封装, 因此sendto接口发送的数据不能超过64K-8个字节, 当sendto发送的数据长度大于64k-8时, 用户就需要在应用层对数据进行分包操作, 但是UDP协议是不可靠的, 所以在应用层进行分包操作的同时也需要进行包序管理,所以数据报长度决定了UDP数据报只能整条发送,整条接受
    Linux---TCP/IP协议栈中应用层与传输层常见协议--实现与特性_第3张图片
    udp在协议栈层面实现;额广播功能, 能够通过向一个IP地址发送数据, 实现将数据发送到局域网所有的主机上

在传输层基于UDP实现的应用层协议 : DHCP协议(动态地址分配), DNS协议(域名解析协议)

TCP协议: 传输控制协议

特性 : 面向连接, 可靠传输, 提供字节流服务
TCP协议的协议字段如下图
Linux---TCP/IP协议栈中应用层与传输层常见协议--实现与特性_第4张图片

  1. 16位源端口与16位目的端口 : 描述数据的流向
  2. 32位序号与32位确认序号 : 进行TCP协议栈中的包序管理
  3. 4位首部长度 : 描述TCP协议的首部长度 , 长度单位是4个字节, 因此头部长度最大的是15*4个字节, 最小的长度是20个字节, 因此TCP的首部大小是不定长的
  4. 保留6位
  5. 6位标志位
    URG : 用来描述紧急指针是否有效
    ACK : 在建立连接是描述确认信号是否有效 , 同一建立连接的时候发送的数据报中就会将ACK置为1.
    PSH : 提示接受数据端应用程序立刻从TCP缓冲区中取走数据
    RST : 双端需要重置连接时, 将RST置为1发送过去, 因此将带有RST的报文称为复位报文段
    SYN : 请求建立连接时, 将SYN置为1发送, 因此将带有SYN的报文称为同步报文段
    FIN : 通知对方, 断开连接, 因此称携带FIN标识的为结束报文段
  6. 16位窗口大小
  7. 16位校验和 : 校验数据一致性
  8. 16位紧急指针 : 配合标志位中的URG使用, 该字段只有URG被置位是才有意义, 表示那部分数据是紧急数据.
  9. 40位选项数据

具体特性协议的实现

面向连接

TCP的面向连接特性体现在 : 创建连接时的 三次握手协议 与断开连接时的 四次回收协议.

三次握手协议

TCP在建立连接时, 首先会由客户端发起建立连接的请求 (STN),服务端收到请求之后,回向客户端的请求进行回复 (ACK),并且为了确保安全, 服务端也会像客户端发送SYN,(ACK与SYN在同一条数据中),客户端在收到SYN后也会像服务端进行回复(ACK), 到这一步三次握手协议完成, 连接建立成功

  • 在建立连接的过程中, 客户端与服务端也会伴随中各个状态的变化, 服务端他在进行套接字绑定之后, 就会进入到一个 LISTEN(监听状态) , 而客户端此时发送了SYN连接请求之后,就会进入一个等待ACK回复的状态, 我们把这个状态称为SYN_SENT, 服务端在恢复了ACK与自己的SYN之后, 就会进入一个等待ACK的SYN_RECV状态, 客户端收到了服务端的SYN, 并且发送了自己的ACK之后,就会进入ESTABLISHED(就绪状态),最后服务端收到ACK之后也会进行入ESTABLISHED状态. Linux---TCP/IP协议栈中应用层与传输层常见协议--实现与特性_第5张图片
    注意 : 双方的这些状态都会有固定的需求,就比如服务端目前处于SYN_RECV状态,他需要接受客户端的ACK, 但此时收到的数据如果不是ACK, 服务端就会向客户端发送将RST标志置为1的报文, 进行重新建立连接
面试问题 : 为什么TCP建立连接是三次,而不是四次或者两次

服务端在接受到了客户端的SYN之后, 无法知道此时的客户端是否具备收发数据的鞥能力,因此需要服务端在向客户端发送SYN确保双方都具有收发数据的能力;由于SYN与ACK在数据包中就是一个标志位, 所以服务端在向客户端进行回复的时候,可以直接将报头中的AYN与ACK标志位置为1发送过去,就可以节省资源. 因此建立连接时, 四次没有必要,两次不安全, 三次刚刚好

四次挥手协议

当客户端要与服务端断开连接的时候, 会首先向服务端发送一个将FIN标志位置为1的数据包, 服务端收到客户端发送的FIN包之后, 会进行一个ACK确认回复, 并且也会向客户端发送一个FIN包(这两条数据是分开发送), 收到服务端发送的FIN包后, 客户端也会发送一个ACK确认.最后连接断开

注意 : 发送FIN包的时机就是在关闭套接字的时候, 对应代码中就是执行**close()**语句的时候.

  • 在客户端调用了close(), 并且发送FIN包之后, 客户端同时会进入一个FIN_WAIT1状态, 当服务端接受到客户端发送的FIN包之后, 会对其回复一个ACK做出确认请求同时服务端进入COLSE_WAIT状态, 由于服务端也要向客户端发送FIN包 , 因此, 在客户端接受到服务端发送的ACK确认信息之后, 客户端就会进入到FIN_WAIT2状态, 等待服务端发送FIN包, 在服务端发送了FIN包之后, 就会进入LAST_ACK状态, 等待客户端发送ACK确认信息, 而客户端在接受到FIN包之后就会进入TIME_WAIT状态, 并且在等待一定的时间之后,释放套接字, 进入CLOSE状态, 服务端在接收到客户端的ACK之后, 释放套接字,进入到CLOSE状态Linux---TCP/IP协议栈中应用层与传输层常见协议--实现与特性_第6张图片

注意 : 服务端接受到FIN包和给客户端发送FIN包之间的COLSE_WAIT状态是为了满足recv()接口的返回值, 当服务端在调用recv接口时, 由于此时客户端有了断开连接的请求 , 处于CLOSE_WAIT状态下recv的返回值就是0, 通过返回值就回去调用服务端套接字的CLOSE()接口, 从而向客户端发送FIN包

面试问题 : 为什么TCP断开连接是四次

倘若TCP断开连接只是三次, 当接受数据的缓冲区中存有大量的数据时, 这时客户端向服务端发送FIN包,服务端立马就会回复ACK并且在发送FIN包,这样通信双方就会立即调用close接口, 释方套接字,而缓冲区中的数据也会丢失, 但是何时调用套接字是有用户自己操作的, 关闭之前将缓冲区之间的数据取走,在通过返回值关闭套接字

面试问题 : TIME_WAIT状态可以不要吗?

如果没有TIME_WAIT, 直接进入CLOSE状态,就会释放socket, 则不会再占用地址信, 此时就会造成最后一次ACK的丢失, 而服务端由于接收不到最后一次的ACK,就会处于LAST_ACK状态.

倘若此时立即重启一个新的客户端 , 它使用的端口与上一次使用的端口一样 , 这样就会对新连接产生影响, 同时伴随着两个问题 :

  1. 使用和上一个socket同样端口的客户端,在与客户端进行建立连接发送SYN的时候,由于此时的服务端在等待ACK的到来,这样就会造成服务端对发送数据的判读失误,从而导致服务端向客户端发送了RST重置链接数据报.
  2. 由于服务端长时间等不到客户端发送的ACK, 他就会再一次向客户端发送FIN包

而TIME_WAIT的作用就是当最后一次ACK丢失时, 就可以对服务端第二次发送的FIN包进行处理, 并且重传的数据也不会对新连接造成影响
等待的时间就是两个MSL(一个报文的最大生命周期, 报文要不被对端收到,要不就消失在网络中)

TCP协议栈中的保活机制

默认情况下通信双方7200秒没有数据往来, 则向对方发送探测数据包,要求对方进行
响应,若得到响应则认为链接正常. 探测数据包每个75秒发送一次,若是连续9次没有
的到响应, 则认为链接断开, 将socket的状态置位CLOSE_WAIT.

可靠传输

TCP是通过确认应答机制超时重传机制实现可靠传输的.
确认应答机制 : 保证可靠传输,最简单的方法就是,接收到数据之后,通知对方我已将接受到数据. 确认应答机制就是通过TCP报头中的序号与确认序号来实现的, 假设客户端发送一个序号为1,长度为1024的数据包, 服务端在收到的时候,就会向客户端发送一个确认序号为1025的数据包, 表示1025之前的数据都已将收到了.
超时重传机制 : 当发送端在一定的时间内没有接收到接收端发送的ACK确认信号的时候, 就认为这一次发送的数据有可能丢失了, 则会对这条数据进行一个重传, 等待的时间默认为200ms

避免丢包

由于可靠传输的实现 , TCP协议通过协头中的序号与确认序号,对TCP协议的数据报进行了包序管理 . 然而如果在传输过程中产生大量的丢包 , 超时超时重传机制就会在传输速度上有了很大的下降, 因此我们要通过滑动窗口机制避免传输过程中的丢包.

滑动窗口机制

倘若发送端发送数据时是,等到接收端的ACK之后再发送下一条的话,这样等待时间就是每一条数据报等待时间的和, 这样浪费了跟大部分的性能. 而我们如果一次性发送多条数据, 这样就将原本多条数据的等待时间进行了压缩, 这样就节省了资源.

而TCP在三次握手建立连接的时候,会协商他们的MSS(最大数据包大小), 而发送缓冲区中的数据会根据MSS进行分包,在TCP头部中有一个滑动窗口大小, 滑动窗口机制就根据MSS将窗口中的数据分段之后,一次性发送过去.接收端将数据接受到之后, 回复的ACK中不就包含有数据的确认序号, 还有接受缓冲区中窗口的剩余大小.
他们通过两个窗口的两端的序号的移动来实现, 对流量的控制

  • 流量控制 : 接受方每次接受到数据后,通过对发送放回复自己窗口的剩余大小, 限制对方所能发送的最多数据 , 避免因为发送的数据过多而造成的的缓冲区的满溢从而造成的数据丢包
  • 快速重传机制 : 在连续发送数据的过程中, 若接收方接收到了第二条数据, 但是没有接受到第一条, 那么就认为第一条数据丢失了, 则会想发送端发送第一条数据的重传请求, 并且发三次. 若发送方连续接受到了三条重传请求,在会重新想接受端发送.
  • 拥塞控制 : 为了保证网络的质量不影响传输数据的性能, 因此在第一次传输的时候传很小的一部分数据(小到一个字节都有可能), 之后每一次传输的大小都比上一次有所增加(可以是成指数的增长);倘若在传输过程中丢包,则传输的大小进行降低,之后在一点一点增加。总结而言,就是:慢启动,快增长,避免因为网络罗状态不好而造成丢包。

提高性能

  • 捎带应答机制

在同信中,通信双方是可以进行互相的数据发送, 在这之中, 如果将ACK单独的作为一个空报头发送到对端,这样也会在成不必要的资源浪费. 倘若某一方收到数据后,也要向对方发送数据, 就可以将要回复的ACK添加到要发送的数据的报头中. 这样一来就大大减少了不必要的空报头的传输

  • 延迟应答机制

收到数据之后, 并不立即进行确认回复, 如果立即进行确认回复, 则会造成窗口变小, 网络吞吐量的降低, 则传输速度就会降低, 延迟是为了保证这段时间有可能用户会将数据取出, 则尽可能保证窗口的大小

提供字节流

tcp调用send接口, 是将数据放在缓冲区中, 操作系统在合适的时间取出合适的大小,
将数据发送出去 对于send/recv接受与发送的数据大小,没有大小限制, 传输灵活,
但是tcp在数据传输是会造成数据粘包的问题
多条数据在发送缓冲区 / 接受缓冲区中合成一条数据----tcp在传输层对数据的边界
不敏感----本质原因

解决方法 : 在应用层进行数据边界管理

  1. 以间隔符区分
  2. 数据定长
  3. 在应用层协议中定义数据长度

你可能感兴趣的:(Linux)