应用层主要用于负责应用程序之间的数据沟通
应用层的协议一般有用户自己制定, 但也不缺乏一些有大佬制定出来,被人们广泛认可的知名协议
假如要实现一个网络版计算器
客户端 : 将两个数字和一个运算符号传给服务端
服务端 : 服务端对接受到的数据进行解析,运算,之后将运算的结果返回给客户端
假设有约定 : 我们将两个数字与一个运算符用一个结构体保存起来,发送数据时将结构体按照一定的规则转化为字符串 ; 接收到数据时,在将字符串转换为结构体这就是一个私有协议
其中我们把结构体转换为字符串的过程称为序列化,反过来的过程称为反序列化 : (序列化:将数据对象按照指定的协议在内存中进行排布, 排布成为可持久化存储或者可数据传输的数据串)
http协议称为—超文本传输协议: 他早期用于超文本文件的传输,在传输层基于TCP实现应用层协议, 并且明文传输 https协议是在http协议的基础上添加了一层加密
协议名称 : 注释使用了应用层的哪个协议
登录信息 : 一般为用户登录的账号与密码
服务器地址 : 表示服务器的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)+第二个字符转换为数字
片段标识符 :
首行 : 主要描述当前协议的版本以及请求的资源路径 / 请求方法 / 状态响应
头部 : 更加细致的描述本次数据的一些详细信息
空行 : 用来间隔头部与正文
正文 : 具体传输给对端的数据
首行请求 : 格式为-----请求方法 + 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
Cookie和Session及两者的联系
Cookie : 服务器发送到用户浏览器并保存在本地的一小块数据,它会在浏览器下次向同一服务器再发起请求时被携带并发送到服务器上。通常,它用于告知服务端两个请求是否来自同一浏览器,如保持用户的登录状态.
Session : 代表着服务器和客户端一次会话的过程。Session 对象存储特定用户会话所需的属性及配置信息.
联系 : 由于http协议是无状态的协议 , 所以浏览器并不知道是哪个用户在和服务器打交道, 这就需要一个机制来告诉服务器. 而Cookie和Session就是完成这个任务的.
当用户第一次在浏览器上登录时 , 服务器根据用户的信息就会在服务端创建一个Session, 并且将Sessoin的唯一表示SessionID返回给浏览器, 浏览器接受待SessionID之后, 就会将其保存在Cookie中,同时 Cookie 记录此 SessionID 属于哪个域名
当用户第二次访问服务器的时候,请求会自动判断此域名下是否存在 Cookie 信息,如果存在自动将 Cookie 信息也发送给服务端,服务端会从 Cookie 中获取 SessionID,再根据 SessionID 查找对应的 Session 信息,如果没有找到说明用户没有登录或者登录失效,如果找到 Session 证明用户已经登录可执行后面操作
传输层的主要作用是负责端与端之间的数据传输
传输层得主要协议是UDP协议与TCP协议
特性: 无连接, 不可靠, 面向数据报
UDP协议的协议字段一共有四个部分 : 16位源端口, 16位目的端口, 16位数据包长度 , 16位校验和
16位源端口与16位目的端口描述了数据从流向
16位校验和 : 判断接受的数据与发送的数据是否一致, 利用二进制反码求和算法进行判断
二进制反码求和算法 :一开始的数据报中的校验和位置是没有数据的, 当数据头部封装好之后, 整个数据报从第0个字节开始,进行取反相加,一直都最后一个字节, 由于校验和的长度只有16位(两个字节),因此当相加的数超出两个字节时, 就从第16位截断, 将16位之后的高位在进行相加, 如此直到所有字节相加完, 相加完成之后将求和所得到的数据放入到16位校验和的位置 ; 当接收到数据的时候, 由于校验和的位置有数据, 再将整个数据报从第一个字节取反码求和后,所得的数据刚好是 0 , 这就说明发送与接受的数据报是完整的.
16位数据报长度 : 描述数据报的大小(包含头部)
由于数据报的长度只有16位, 因此一个数据报的最大长度只能有64K. 由于报头是UDP协议进行封装, 因此sendto接口发送的数据不能超过64K-8个字节, 当sendto发送的数据长度大于64k-8时, 用户就需要在应用层对数据进行分包操作, 但是UDP协议是不可靠的, 所以在应用层进行分包操作的同时也需要进行包序管理,所以数据报长度决定了UDP数据报只能整条发送,整条接受
udp在协议栈层面实现;额广播功能, 能够通过向一个IP地址发送数据, 实现将数据发送到局域网所有的主机上
在传输层基于UDP实现的应用层协议 : DHCP协议(动态地址分配), DNS协议(域名解析协议)
特性 : 面向连接, 可靠传输, 提供字节流服务
TCP协议的协议字段如下图
TCP的面向连接特性体现在 : 创建连接时的 三次握手协议 与断开连接时的 四次回收协议.
TCP在建立连接时, 首先会由客户端发起建立连接的请求 (STN),服务端收到请求之后,回向客户端的请求进行回复 (ACK),并且为了确保安全, 服务端也会像客户端发送SYN,(ACK与SYN在同一条数据中),客户端在收到SYN后也会像服务端进行回复(ACK), 到这一步三次握手协议完成, 连接建立成功
面试问题 : 为什么TCP建立连接是三次,而不是四次或者两次 |
服务端在接受到了客户端的SYN之后, 无法知道此时的客户端是否具备收发数据的鞥能力,因此需要服务端在向客户端发送SYN确保双方都具有收发数据的能力;由于SYN与ACK在数据包中就是一个标志位, 所以服务端在向客户端进行回复的时候,可以直接将报头中的AYN与ACK标志位置为1发送过去,就可以节省资源. 因此建立连接时, 四次没有必要,两次不安全, 三次刚刚好
当客户端要与服务端断开连接的时候, 会首先向服务端发送一个将FIN标志位置为1的数据包, 服务端收到客户端发送的FIN包之后, 会进行一个ACK确认回复, 并且也会向客户端发送一个FIN包(这两条数据是分开发送), 收到服务端发送的FIN包后, 客户端也会发送一个ACK确认.最后连接断开
注意 : 发送FIN包的时机就是在关闭套接字的时候, 对应代码中就是执行**close()**语句的时候.
注意 : 服务端接受到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状态.
倘若此时立即重启一个新的客户端 , 它使用的端口与上一次使用的端口一样 , 这样就会对新连接产生影响, 同时伴随着两个问题 :
而TIME_WAIT的作用就是当最后一次ACK丢失时, 就可以对服务端第二次发送的FIN包进行处理, 并且重传的数据也不会对新连接造成影响
等待的时间就是两个MSL(一个报文的最大生命周期, 报文要不被对端收到,要不就消失在网络中)
默认情况下通信双方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在传输层对数据的边界
不敏感----本质原因
解决方法 : 在应用层进行数据边界管理