一. 再谈HTTP再理解
协议的本质: 双方的一种约定,规则,双方需要按照相同的一套处理机制(协议)进行处理
应用层协议对应的是一个服务, FTP文件传输协议, NDS域名解析协议, HTTP超文本传输协议, 这些是协议同时也对应着一个服务.
http协议的本质 : http协议的本质是要获得某种资源 (视频,音频, 网页,图片等)
实际上,上网的大部分行为都是在进行进程间通信.. 也就是不断地获取信息和发送信息
比如:
1. 把服务器上面地资源数据拿到本地 (短视频, 网页)
2. 把本地地数据推送到服务器 (搜索, 注册,登录,下单)
- http的底层一般是基于传输层协议tcp实现的
- 浏览器和web服务器三次握手建立TCP连接
- 浏览器进行 req (request请求)
- 服务器进行 rep (reply响应)
- 浏览器和web服务器四次挥手断开TCP连接
- http是超文本传输协议, 在底层实现中涉及了很多地文本解析
再谈http地无状态: 指协议不具备记忆地能力, 不需要对于进程间通信地历史状态进行保存, 服务器是无法判断用户是否历史上曾经打开过这个网页了的. 也就是上一次打开网页和这一次打开相同的网页互相不关联, 也不知道你上次打开过. (不会记忆)这个就叫做无状态
request 报文
- response 报文
在写HTTP服务器的时候我们必须按照上述的报文格式进行书写
在整个收发请求数据和应答数据报文的时候会进行报文的解析, 之所以每一个结束位置都是一个CRLF,就是回车换行的意思, 所有的信息以一行一行的形式进行呈现出来
空行出现的原因: 作为报头和有效载荷的分割符号. 循环着一行一行的读取, 直到读取到空行代表报头读取完毕
主要请求方法解释:
GET : 直接获取对应的资源信息(eg: 网页) 资源从哪来 URL定位
POST : 将数据交给服务器, 通过正文提交, 不需要URL定位
上述便是需要Content-Length的原因, 获知正文是否存在以及正文长度
- 再次手写一个便理解的简单的httpserver.cc 的服务器, 使用C++完成
#include <iostream> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <strings.h> #include <signal.h> #include <string> #include <fstream> #define ERR_EXIT(m) \ do { std::cerr << m << std::endl; close(EXIT_FAILURE); } while(0) typedef struct sockaddr SA; int main() { signal(SIGCHLD, SIG_IGN);//信号处理, 避免子进程僵尸 int listenfd, connfd, pid; socklen_t addLen; struct sockaddr_in clientAdd, serveAdd; if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ) { ERR_EXIT("socket"); } int flag = 1; setsockopt(listenfd,SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); //SO_REUSEADDR BOOL 允许套接口和一个已在使用中的地址捆绑(参见bind()) //SOL_SOCKET固定level设置 bzero(&serveAdd, sizeof(serveAdd));//清空 serveAdd.sin_family = AF_INET;//协议家族 serveAdd.sin_port = htons(8080);//默认80端口 serveAdd.sin_addr.s_addr = htonl(INADDR_ANY); //INADDR_ANY == 0 作用适配地址 if (bind(listenfd, (SA*)&serveAdd, sizeof(serveAdd)) == -1) { ERR_EXIT("bind"); } //3: 等待队列syn队列的长度 if (listen(listenfd, 3) == -1) { ERR_EXIT("listen"); } while (1) { if ((connfd = accept(listenfd, (SA*)&clientAdd, &addLen)) == -1) { ERR_EXIT("accept"); } pid = fork();//fork出来子进程 if (pid) { close(connfd); //父进程关闭connfd然后仅仅进行listen. SIGCHLD会自动收尸 } else { close(listenfd);//子进程进行发送响应报文 char buffer[1024]; recv(connfd, buffer, sizeof(buffer), 0); std::cout << "#############################http request begin#############################################"<<std::endl; //打印recv的来自客户端的请求并且输出请求信息 std::cout << buffer << std::endl; std::cout << "#############################http request end#############################################"<<std::endl; std::ifstream ifs("./index.html"); if (ifs) { int len; ifs.seekg(0L, ifs.end);//定位到文件末尾 len = ifs.tellg();//获取文件长度 ifs.seekg(0L, ifs.beg);//回到文件开头 char *file = new char[len]; ifs.read(file, len);//读取正文, 字符串形式读取 ifs.close(); //开始处理响应: std::string status_line = "HTTP/1.1 200 OK\r\n";//状态行 std::string reply_header;//响应头部 reply_header +="Content-Length: " + std::to_string(len) + "\r\n"; std::string black = "\r\n"; send(connfd, status_line.c_str(), status_line.size(), 0); send(connfd, reply_header.c_str(), reply_header.size(), 0); send(connfd, black.c_str(), black.size(), 0); send(connfd, file, len, 0); delete [] file; } close(connfd); exit(EXIT_SUCCESS); } } close(listenfd); return 0; }
服务器IP : 端口号 可以访问, 服务器可能没有开放端口, 可以在购买的服务器安全组中设置
- 再次图解小结HTTP, 整个协议栈的角度去看
注意: 这个http应用层简单的理解是直接和对端建立了连接好似是直接和对端进行请求响应的交互方式, 然后一次连接完成之后立马断开, 但是其实真正传输数据包的时候, 是需要贯穿整个协议栈的, 也就是http请求是将自己的数据传给下层的, 是有封包和解包的过程的, 不理解可以看入门篇
再强调一下http叫啥: 超文本传输协议, 其实蛮简单的, 就是传输的文本, 文本内容在数据报文的正文中,正文前面有报头请求行(请求), 或者是报头和状态行(响应).... 报头和有效载荷的分离依赖的是空行.
请求行: 请求方法(GET, POST) URL:资源定位 协议版本(HTTP/1.0 HTTP/1.1 ...)
响应状态行: 协议版本, 状态码 状态码描述信息。。。
中间的各种 键: 值 对(首部字段名: 值) 都是各种细节信息 eg : Content-Length
空行: 分隔报头和有效载荷(正文)
二. HTTP对比学习HTTPS
HTTP的请求信息是明文传输, 容易被窃取
HTTP不会验证对方的信息, 存在被冒充的风险
数据的完整性没有校验, 容易被中间人篡改
为啥叫做HTTPS , S的含义, SSL:加密,在HTTP下加入SSL层 (解决上述问题)
SSL操作步骤:
验证服务器端
允许客户端和服务端选择加密算法和密码, 确保双方都支持
验证客户端
使用公钥加密技术来生成共享加密数据
创建一个加密的SSL连接
基于该SSL连接传递HTTP请求
HTTPS的主要作用:一个是建立一个信息安全通道,用来保证数据传输的安全性,另外一个就是验证网站的真实性了...
HTTP和HTTPS的区别如下:
https协议需要 ca申请证书,一般免费的证书较少,因而是需要一定费用的]
http是超文本传输协议,信息是明文传输,https则是具有安全性的SSL加密传输协议
http 和 https使用的是完全不同的连接方式,用的端口也是不一样的。前者是80端口 后者是443端口
http的连接很简单,是无状态的;https协议是由 SSL + HTTP协议构建的可进行加密传输,身份认证的网络协议,比http协议安全.
在OSI模型中,HTTP工作在应用层,而HTTPS工作在传输层写传输层协议之前先介绍一个四元组的概念: 网络通信的实现就是基于四元组的,不论是TCP还是UDP 要想将数据从一端传入到另外一端,就必须明确对端的二元组, 双方如果都要相互通信就要确定四元组
首先我们确定要双方不同主机上的不同进程间进行通信, 必须确定双方的 ip + port why?
- 上述图主要是为了引出为啥要 ip
- 上述图是为了引出为啥需要 port
- 至此我想聪明的大家自然是明白了为啥一定要确定四元组双发才能通信了
- 上述四元组可谓是特别重要的一个铺垫了, 后序的TCP最大连接数目等等咱要分析清楚其实都还得必须从上述这个得限制入手了. (这个也是面试的常考点)
三.TCP协议 (三次握手四次挥手细节过程理解在之前的博文中有详细图解)
- 报文分析
源 / 目的端口,那就是老身常谈了, 从哪个进程来,到哪个进程去
32位序号和确认序号 (原谅咱留个小疑惑,后面解释,和重传机制有关系)
4位TCP报头长度: 表示该TCP头部有多少个32位bit(有多少个4字节); 所以TCP头部最大长度是15 * 4 = 60 (基本衡量单位都是4字节为最小单位) 因为首部长度是4位 最大就是15; 所以最大首部长度就是 15 * 4 (最小单位) = 60字节
6位标志位:
URG: 紧急指针是否有效
ACK: 确认号是否有效
PSH: 提示接收端应用程序立刻从TCP缓冲区把数据读走
RST: 对方要求重新建立连接; 我们把携带RST标识的称为复位报文段
SYN: 请求建立连接; 我们把携带SYN标识的称为同步报文段
FIN: 通知对方, 本端要关闭了, 我们称携带FIN标识的为结束报文段
16位窗口大小(先留个疑,其实就是存储接收缓冲区还剩下的大小, 和流量控制有关)
16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也 包含TCP数据部分
6位紧急指针: 标识哪部分数据是紧急数据
tcp缓冲区概念的引入 (解释流量控制):
tcp连接建立之后是存在接收和发送内核缓冲区的....
send 还有 recv这些接口都不是直接将数据发送到网路中,也不是直接从网路中读取数据的...
而是存在发送缓冲区和接收缓冲区的概念, 这些都是内核缓冲区。。。同样之前的窗口大小其实就是接收缓冲区还剩余的大小, 支持流量控制 (你发送的数据不能超过,不然就满了)
- 接收缓冲区大写也对应着流量控制:why? 如何理解
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control)
所以窗口大小就是接收端处理能力的代表 (发送端接收到窗口大小会根据窗口大小进行调节自己的发送速度,进行流量控制) 填充窗口字段就是填充的自身的接收缓冲区中剩余空间的大小 (此窗口也称为接收窗口)
窗口大小字段越大, 说明网络的吞吐量越高;
确认应答(ACK)机制的理解 (编序号)
- ACK应答的含义就是: acknum 之前的序号的数据包我都已经收到了,下一次你从acknum开始发送吧
超时重传机制
超时重发指的是因为网络环境的拥堵阻塞导致了在很长一段时间发送方都没有等到自己之前发送包的回音 (于是TCP默认是丢包)然后会采取重传措施
由于重传机制,会存在一些情况是 之前因为网络拥堵的数据包 在网络环境恢复之后正常传入到对端, 导致对端可能会收到多份重复的数据报文,不过嘞因为序号的存在可以通过需要进行简单的去重即可
但是这个超时时间如何确认??? 多少算合适,这个也是个置得讨论的问题
最理想的情况下, 找到一个最小的时间, 保证 "确认应答一定能在这个时间内返回".
但是这个时间的长短, 随着网络环境的不同, 是有差异的.
如果超时时间设的太长, 会影响整体的重传效率;
如果超时时间设的太短, 有可能会频繁发送重复的包;
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间.(抓核心主题,据网络环境而生 超时时间)
Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时 时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.
如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接.
滑动窗口理解
首先滑动窗口的前提是基于缓冲区来实现的, 没有缓冲区是做不到滑动窗口的,流量控制同样也是做不到的
滑动窗口的出现是根据缓冲区大小来进行一次发送多条数据来提升性能...
可以思考一下反正我需要发很多数据,1 - 1000 1001 - 2000 2001 - 3000 ... 我是可以采取一条一条的发送,等待一条有了回音,再继续发送下一条,可是如果窗口是足够的情况下我可以一次发送多条数据,这样可以将每条数据的等待应答时间重叠起来,实现效率性能的提升...
如同上图这般,同时发送多条数据 (将多条数据的等待应答时间压缩成一条数据的等待应答时间)
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值. 上图的窗口大小就是3000个字节(3个 段). 这个称为发送窗口大小 (是根据所在端的发送缓冲区大小和对端的接收缓冲区的大小中取出最小值来确定的)
发送前四个段的时候, 不需要任何等待,直接可以发送
收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据; 依次类推
操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确 认应答过的数据, 才能从缓冲区删掉
窗口越大, 则网络的吞吐率就越高
滑动窗口下的丢包问题分析
- 第一种是ACK丢失
- 如上述情况下,滑动窗口的ACK部分丢失其实不是很紧要,因为可以通过后序的ACK确认;ACK一旦确认之后代表的含义是 默认之前的所有序列数据都已经全部收到了
- 情况2是数据包传过去的时候就丢失了
当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001" 一样
然后; 如果发送端主机连续三次收到了同样一个 "1001" 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已 经收到了, 被放到了接收端操作系统内核的接收缓冲区中 (提前收到的后序的报文也不会丢弃, 只是前面的没有确认 后面的也就无法确认,因为一旦确认就默认前面的所有数据全部已经收到了)
这种机制被称为 "高速重发控制"(也叫 "快重传").
单独解释一下 快重传 : 就是说在接收方收到一个失序的报文段的时候就立即会发出重复确认。(目的在于使得发送方尽早地知道说自己有报文丢失了,没有到达对面)接收方地意思就是 哥你确定你发的是对的,我前面的报文都还没收到 (顺序不对呀)三次之后发送方反应过来直接重传,不再等待超时