目录
三.TCP协议 (三次握手四次挥手细节过程理解在之前的博文中有详细图解)
UDP的特征: 什么是无连接,不可靠,关键为什么它如此的不稳定但是在现在的短视频 音视频通话 DNS ARP这些全部都还使用的是UDP作为传输层协议
根据上述的延迟解释一下音视频通话为例解释下为啥使用UDP而不是TCP?
前文链接(系列助学, 也为后文学习做铺垫, 可按需读取)
- 网络入门篇. 从操作系统整个体系结构层面推开网络大门. 让你对网络有个整体的宏观认识
- 网络实战篇(让你对于TCP编程从0到实现最终面试常考百万级别高并发服务器)
- 三次握手,四次挥手, HTTP图解理解学习,面试八股文再也不用死记硬背了
一. 再谈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
- HTTPS : 是以安全为目标的HTTP通道, 通俗说就是安全版本的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)接收端其实之前就已 经收到了, 被放到了接收端操作系统内核的接收缓冲区中 (提前收到的后序的报文也不会丢弃, 只是前面的没有确认 后面的也就无法确认,因为一旦确认就默认前面的所有数据全部已经收到了)
- 这种机制被称为 "高速重发控制"(也叫 "快重传").
- 单独解释一下 快重传 : 就是说在接收方收到一个失序的报文段的时候就立即会发出重复确认。(目的在于使得发送方尽早地知道说自己有报文丢失了,没有到达对面)接收方地意思就是 哥你确定你发的是对的,我前面的报文都还没收到 (顺序不对呀)三次之后发送方反应过来直接重传,不再等待超时
拥塞控制
- (滑动窗口) 大小决定 min( 接收窗口决定的 , 拥塞窗口决定 (发送方发送缓冲区大小))
- why需要慢开始,最一开始发送方会将发送窗口(拥塞窗口的)设置的很小 ?
- 因为网络环境错综复杂,刚开始不清楚网络环境的好坏, 所以满开始就有点像是派个侦察兵去看看,网络环境咋样,然后再进一步调整拥塞窗口的大小 确定滑动窗口大小..
- 先探测一下当前的网络拥塞程度,然后由小到大的逐渐增大拥塞窗口的大小.
- 然后是指数级的扩张滑动窗口大小,但是也不能一直那样扩大下去,于是有一个阈值的概念,超过这个阈值又转变为线性增长了
- 当TCP开始启动的时候, 慢启动阈值等于窗口最大值
- 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回
- 少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
- 当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降
- 其实就是为了保证在不造成网络环境压力太大的情况下尽快将数据传输过去
TCP小结
- TCP是面向连接的,可靠的,基于字节流的传输层通信协议
- 如何保证可靠性:
- 性能提升上面:
- 1. 采取了滑动窗口
- 2. 快速重传 (不需要等待超时,三次对端提醒之后自动重传)
基于TCP应用层协议 HTTP HTTPS SSH Telnet FTP SMTP
TCP最大连接数的分析(面试常考)(从四元组的角度入手)
- 客户端 和 服务器之间建立连接 : 只要确保客户端的 ip + 客户端的 port 两个中存在一个和服务端不同即可建立连接.....
- 理论最大 : 最大TCP连接数目 = 客户端ip数目 * 客户端端口数目
- ipv4而言理论ip 数目最多是 2 ^ 32 port 数目是 65535 = 2 ^ 16, 所以理论最大的TCP连接数目是 2 ^ 48
- 但是并发数目是万万不可能达到上述这么多的. (考虑并发) 首先第一个就是文件描述符的限制 sockfd数量限制,当然这个可以在服务器中修改配置... ulimit
- 还有就是内存的限制了,TCP存在发送和接收内核缓冲区, 你用户空间也还要开缓冲区,所以肯定是无法达到上述哪个恐怖的数目的
四. UDP协议
- 先从报文分析入手
- 源端口号: 客户端端口号.
- 目的端口号:服务器端口号, 负责确定交付给哪个应用程序.
- 一个应用程序可以绑定多个端口号,但是一个端口号一定是对应一个应用程序.(端口号标识唯一应用程序)
- 16位UDP长度, 表示整个数据报(UDP首部+UDP数据)的最大长度
- 如果校验和出错, 就会直接丢弃
-
报头理解, 报头实现地方式, 使用C语言中地位段进行理解: 多少位分为一段
首先是无连接, 连接是什么: 连接算是一端到另外一端的不存在的一根线 (抽象的来说,这个是我的个人理解, 连接的过程也就是三次握手的过程)对于三次握手不理解的可以看我前文链接存在详解
首先不论是有连接还是无连接, 我们核心应该确定的是什么? 确定四元组,对了四元组,无连接也可以进程间通信,只不过每一次必须传入四元组, 但是口说无凭, 咱看看接口呗。对比一下:
- 为什么UDP是不可靠连接?
因为UDP没有TCP的哪些为了保证可靠性的机制: 比如超时重传机制,拥塞控制,流量控制机制,为数据包编号排序等...
- 思考为啥UDP如此不可靠我们还必须要使用它, 而且还会尽量的使其变得可靠, 还要专门做UDP可靠性设计,这个不是多次一举吗. 不如直接使用TCP?
首先解释UDP相比TCP为什么相对实时性好, 在时间上更短, 更加快速。。。
- 以上是从不必要的三次握手建立连接上解释这个速度问题, 为啥UDP更快
- 而且在进行数据传输的时候TCP还会存在一定的时间限制,时间阈值,超过这个时间就需要进行重传, 重传也会导致延迟性,向我们的qq聊天呀就经常出现这样的延迟现象,很明显底层应当是采取的TCP作为传输层协议.
-
根据上述的延迟解释一下音视频通话为例解释下为啥使用UDP而不是TCP?
- 一句话解释:就是通话延迟的问题,我们qq上发个消息是无所谓,延迟下我们可以等会看嘛,但是你在跟别人搞音视频,像抖音这些,或者各种视频,这个要是通话延迟,几秒前说的话几秒后出来了你这个还搞个屁呀, 还说得清嘛,这个很明显需要实时通话,正是这样的场景存在所以UDP必然是需要的。。。而且现在音视频(短视频)如此火爆更是少不了UDP了
- 我们再从另外一个角度来分析一下这个问题, 服务端压力上面来考虑。。。
再谈UDP可靠传输的设计。。。
- 现有的udp可靠传输协议就是KCP了,感兴趣的还真有必要得去深入研究一下,我个人是研究深度还不够,先暂且浅显的聊一聊这个UDP可靠传输设计的一些基本的东西,KCP要是将来我的理解深入足够会尽力刨析一下...
- 首先既然提到了MTU 先解释一下 MTU是个啥玩也.
- 总结UDP可靠传输的学习:
- 多研究TCP协议的实现
- 设计 udp可靠传输,和TCP不一样,重传策略受到我们的控制可控 (也不一定所有都需要重传) 像音视频,游戏走位 超时太久丢包就丢了,不需要重传... 但是有些其他应用场景下又必须进行重传
- 重传时机的确定 (根据具体的业务需求去设计,不要一味追求设计一个通用性的UDP可靠传输协议)
五. 对比TCP和UDP的细节
- 连接
- TCP是面向连接的传输层协议,传输数据前需要先建立连接
- UDP 是不需要建立连接的。 即可马上立即传输数据 (接口传入二元组)
- 服务对象
- TCP是一对一的两点服务,即一条连接只有两个断点
- UDP支持一对一 ,一对多, 多对多的交互通信
- 可靠性
- TCP是可靠交付数据的,数据无差错,不重复,按顺序到达
- UDP则是尽力交付,不保证可靠 (因为没有各种传输策略)
总结本文
- 本文从再看HTTP 和 对比学习 HTTPS入手
- HTTP协议的学习核心在于搞清楚他是一次性的无状态连接方式,连接建立之后服务结束立马断开连接。。。
- 还有就是搞清楚HTTP的报文格式
- 上述的核心在于最开始的一行请求行 (请求方法 URL 协议版本) + 状态行(协议版本 状态码 状态码描述字段) 搞清楚空行的作用:分割报头和正文
- HTTPS对比 HTTP学习 : 一个是明文传输的角度来看 另外一个是从网站真实性,身份验证,中间数据修改 的角度来看去分析,HTTPS相对 HTTP多了SSL加密层
- 然后是UDP和TCP的学习:
- UDP无连接的 基于一个一个数据包的传输的一种 不可靠传输协议 (但是因为其相对TCP的快速 (实时性更好) + 可制定各种传输策略实现可靠传输 )在音视频通话等领域有着不可替代的作用
- TCP有连接的 基于数据流的可靠传输协议 (可靠的核心在于各种策略机制)
转载:https://blog.csdn.net/weixin_53695360/article/details/123289297