基础引入
应用层
应用层负责应用程序之间的数据沟通。
- 自定制协议
- 序列化:将数据按照持久化存储或网络数据传输的格式进行排布。
- 反序列化:对数据以指定的协议进行解析。
url
:统一资源定位符。(俗称网址)其中包含:
协议名称 :// 用户名 : 密码 @ 服务器地址 : 服务器端口 / 资源路径 ? 查询字符串 # 片段标识符
URL
编码和解码 :
因为url
中特殊字符都具有特殊含义,因此查询字符串(用户提交的数据)中若有特殊字符存在,则会造成数据二义性,因此需要对用户提交的数据进行urlencode
操作。
urlencode
:将特殊字符转换为十六进制字符串,在编码字符前方使用%
表明字符已经编过码,需要解码。urldecode
:当查询字符串中出现%
,则认为后续字符是需要url
解码的。
例如
C++
这几个字符将会编码为C%2b%2b
。
- 知名协议
HTTP协议
http
是应用层协议,在传输层使用tcp
协议实现,默认端口为80
。
(http
是一个短连接,但在http 1.1
中实现了长连接。)
协议格式:请求首行、请求头部、空行、正文。
请求首行
- 请求首行:请求方法(
GET
、POST
等)、URL
、协议版本(HTTP
(0.9 / 1.0 / 1.1 / 2
))。(数据间以空格间隔)
get
所提交的数据在url
中,明文形式,肉眼可以看到,所以不安全。
post
提交的数据在正文中,安全
- 响应首行:协议版本、响应状态码、状态码描述。
状态码:1xx
、2xx
、3xx
、4xx
、5xx
。
常见的状态码:
200
:OK
302
:临时重定向
404
:page not found
502
:bad gateway(坏的网关)
请求头部
头部由一个个键值对的头部信息组成。
键值二者之间以:
(冒号 + 空格)间隔。
每个键值对都有其含义和功能。格式 key:value
\r\nkey:value
\r\n
(连续遇到2
个\r\n
就认为是头部信息结束了)
另外包含Content-Length
/Content-Type
/refer
/Set-Cookie
/Transfer-Encodeing
/Location
信息等。
空行
本质上是:\r\n
正文
什么格式数据都有 ~
使用封装的tcpsocket完成实现一个简单的http服务器
本质上就是在tcp
服务器的基础上进行http
协议数据的解析。
解析过程
- 获取
http
头部(首行 + 头部)
首行中包含url
可以知道客户端申请什么资源,GET
请求还可以获取到提交的数据。
首行中包含的协议版本:拿到版本就可以针对不同版本的特性进行处理。 - 解析头部
可以获取到正文有多长,正文是什么类型的数据。 - 获取正文,进行处理
(通常将正文交给子进程处理)
Q
:如何获取头部/如何保证获取一个完整的头部?A
:可以MSG_PEEK
探测性接受
传输层
传输层负责端与端之间的数据传输(TCP
/UDP
)
- 端口号:在一台主机表示一个进程。
类型:uint16_t
范围:0~65535
(其中0~1023
不推荐使用,存在预留)
操作系统获取到网卡接收的数据,通过端口就知道数据该放到哪一个socket
的缓冲区中。
一个端口号只能被一个进程占用,一个进程可以占用多个端口号。
五元组:
一条网络上的数据包含的五条信息:源ip
+ 源端口 + 目的ip
+ 目的端口 + 协议
主机上网络状态的查看:netstat -anptu
传输层的传输协议
UDP
:无连接、不可靠、面向数据报。要求向应用层整条数据接收、交付,不会出现半个数据,因为头部中有报文长度标识,否则就需要额外维护 / 管理了。
也正是因为数据报长度在协议头中有标识,所以udp
不会产生黏包问题。
面向数据报:数据整条收发,灵活性低,但不会造成黏包问题。
udp
协议包含字段:源端口、目的端口、数据报长度、校验和。
- 源端口/目的端口:传输,确定数据应该哪个进程处理。
- 校验和:二进制的反码求和。
- 数据报长度:
uint16_t
类型。
意味着udp
数据报最大长度为64k - 8
(因为包含8位的udp
头部),若sendto
给的数据大于64k
则会报错。因为udp
在传输层不会进行数据分段。
若传输的数据大于64k
,用户需要在应用层将数据分割成一个个小段,进行传输。
udp
传输并不保证数据报的有序到达。(有可能会乱序),因此也需要用户在应用层进行包序管理。
udp
协议实现了传输层广播数据包。
TCP协议
tcp
协议可以保证包序。
tcp连接管理
面向连接,可靠传输、面向字节流
TIME_WAIT
的作用:
- 假设没有
TIME_WAIT
会有什么情况?
答:最后一个ACK
丢失。
被动关闭方重发FIN
包,没有等待直接关闭。若直接启动的客户端又使用相同端口信息,有可能收到FIN
包,对新连接造成影响。
若新启动客户端使用相同端口信息,向服务端发送SYN
请求,但是被动关闭方(服务端)因为没有收到最后一个ACK
,处于LAST_ACK
状态,收到SYN
后判定状态错误。同时会符RST
报文,重置连接,也对新连接造成影响。
- 因此主动关闭方收到发送最后一个
ACK
之后需要等待一段时间(2
个MSL
时间):
一、 处理若ACK
丢失,导致对端重传的FIN
包。
二、等待网络中所有双方延迟的报文消失在网络中,不会对后续造成影响。
为什么握手是三次,而挥手是四次?
若三次握手的时候第三次握手失败,服务器如何处理?
SYN泛洪攻击?
TIME_WAIT
状态的作用?
为什么服务端出现了大量的TIME_WAIT
连接? 如何解决?
- Q:如果服务端程序
TIME_WAIT
无法理解重启,也可以说是此端口暂时无法使用、无法立即重启 - A:修改参数(
setsockopt
)
可靠传输
确认应答机制、超时重传机制(保证数据安全到达)、协议字段中的序号 / 确认序号(保证数据有序到达)
因为tcp
为了实现可靠传输牺牲了部分传输性能,而且有可能因为ACK
确认应答丢失也要重传数据,因此又提出了以下几种机制来避免大量的丢包重传以及ACK
丢失重传,来提高性能:
- 滑动窗口机制:通过协议字段中的窗口大小,双方进行协商,一次可以最多传输多少数据,之后等待确认应答,不需要进行一对一的停等。
(快速重传机制
+流量控制
+拥塞控制
)
流量控制
:TCP
通过双方协商窗口大小进行流量控制,避免因为缓冲区数据塞满而大量丢包重传。
快速重传机制
:
- 每条数据的确认回复都必须按序回复,如果前面的数据没有收到,则不会对后面的数据进行回复(乱序的情况),意味着若接收到一条回复,表示
ACK
确认序号之前的数据全部安全到达,不会因为前面数据的ACK丢失而重传数据。- 若前面的数据丢失,则接收方收到后面发送的数据,立即发送重传请求,并且
连发三次
。若发送方连续收到三条重传请求,则认为数据丢失,进行重传。
拥塞控制
:慢启动,快增长
。
发送端控制一个拥塞窗口大小,在进行数据传输的时候,进行网络探测式的发送。
- 若网络状况良好,则发送的数据快速增长(指数级),达到阈值(窗口大小)时,则不再继续增长。
- 若传输过程中出现丢包,则重新初始化拥塞窗口。重新慢启动,快增长。
拥塞控制为了避免因为网络状况不好导致通信初始,大量的数据包丢失重传,降低性能。
-
延迟应答机制
接收方收到数据后并不立即进行确认回复,而是等待一段时间,因为这段延迟的时间内,有可能用户已经recv
,将缓冲区中的数据读取走,窗口就可以尽可能的保证最大大小,保证传输吞吐量。 -
捎带应答机制
接收方对每一条的确认回复都需要发送一个TCP
数据报,但是空报头的传输会降低性能,因此会在即将要发送的数据报头中包含有确认信息,可以少发送一个用于确认的空报头。
面向字节流
意为:传输字节流。
特性:传输灵活。
发送方每次调用sendto
都会将数据放到缓冲区中,然后内核选择合适时机发送数据。
接收方网卡接收到数据,都会将数据放到接收缓冲区中,用户recv
就是从接受缓冲区中取数据。
缺点:黏包问题:
- 主要发生的位置在于:发送缓冲区 / 接收缓冲区 中的数据堆积。
- 本质原因:
传输层的TCP
协议栈对数据之间没有明显边界,TCP
只管传输数据的字节流,导致发送端/接收端因为数据的堆积在实际发送或者recv
一次获取到半条或多条数据。 - 如何解决
TCP
黏包?
虽然TCP
在传输层没有数据边界,但是用户可以在应用层进行边界处理。
【边界处理常见方法】:
- 特殊字符处理(如
HTTP
协议) - 定长数据(如
UDP
头中包含长度) - 变长数据,在数据包头汇总声明数据长度。
TCP
有黏包问题,但UDP
没有黏包问题。
TCP
相较于UDP
传输性能较低。
TCP
安全传输,UDP
不安全传输。
TCP
断开连接:
TCP
协议栈有自身的保活机制:长时间无数据通信,则发送保活探测包。若多次没有收到回应,则认为连接断开。