文章目录
- 一、应用层协议
- 1.我写的socket是在干什么?
- 2.再谈"协议"
- 1.问题1
- # 2.问题2
- 3.造轮子——定制应用协议
- 4.使用json来完成应用协议
- 1.安装库
- 2.使用 —— 序列化
- 3.使用 —— 反序列化
- 二、HTTP协议
- 1.认识URL
- 2.http协议的请求格式和响应格式
- 3.编写响应 - GET
- 4.报头
- 1.GET
- 2.POST
- 3.GETvsPOST
- 4.HTTP报头
- 5.状态码3xx
- 6.cookie策略
- 7.Connection
- 三、HTTPS协议
- 1.什么是https
- 2.什么是加密
- 3.为什么要加密
- 4.常见的加密方式
- 1.对称加密
- 2.非对称加密
- 5.数据摘要&&数据指纹
- 6.总结
- 四、传输层
- 1.UDP
- 1.协议端格式
- 2.UDP特点
- 3.UDP缓冲区
- 4.UDP使用注意事项
- 2.TCP
- 1.TCP协议段格式
- 2.确认应答(ACK)机制 - 32位序号/32位确认序号
- 3.流量控制 - 16位窗口大小
- 4.6个标记位
- 5.超时重传
- 6.三次握手和四次挥手
- 7.滑动窗口
- 1.什么是滑动窗口
- 2.滑动窗口如何变化
- 8.流量控制
- 9.拥塞控制
一、应用层协议
1.我写的socket是在干什么?
在写应用层协议。
2.再谈"协议"
1.问题1
协议是一种 “约定”. socket api的接口, 在读写数据时, 都是按 “字符串” 的方式来发送接收的. 如果我们要传输一些"结构化的数据" 怎么办呢?(需要序列化和反序列化)
约定:要传的一共有三个区域,用:分割,前两个是int,后一个是char
协议定制:有几个字段,每个字段什么含义
将这个结构体变成字符串行为叫序列化。
data d={10,20,‘+’} => “10:20:+”
将这个字符串转为结构行为叫反序列化。
“10:20:+” => data d= { 10,20,‘+’ }
而 上面就是在用户发送和网路之间加了一层软件,可以在里面做各种操作。
# 2.问题2
字符串整体的长度对方怎么知道?
我们需要定制的时候,序列化之后,需要将长度,我们设置为4字节将长度放入序列化之后的字符串的开始。——自描述长度的协议
3.造轮子——定制应用协议
4.使用json来完成应用协议
1.安装库
sudo yum install -y jsoncpp-devel
2.使用 —— 序列化
1.Value对象,万能对象
2.json是基于kv
3.json有两套操作方法
4.序列化的时候,会将所有数据内容,转化为字符串
void serialize(std::string *out)
{
Json::Value root;
root["x"] = x_;
root["y"] = y_;
root["op"]=op_;
//方法一:
Json::FastWriter fw;
//方法二:
//Json::StyledWriter fw;
*out =fw.write(&root);
}
方法一(推荐):
方法二:
3.使用 —— 反序列化
// 反序列化
bool deserialize(std::string &in)
{
Json::Value root;
Json::Reader rd;
rd.parse(in,root);
x_ = root["x"].asInt();
y_ = root["y"].asInt();
op_ =root["op"].asInt();
return true;
}
二、HTTP协议
https://www.baidu.com/
a.域名—>必须转化为ip
b.访问网络服务,服务端必须具有port
网络通信的本质:socket ,IP+port
使用确定协议的时候,一般显示的时候,会缺省端口号,所以,浏览器访问指定的url的时候,浏览器必须给我们自动添加port,浏览器如何得知,url匹配的port是谁?特定的众所周知服务,端口号必须是确定的!
httpserver->80
httpsServer->443 sshd->22
用户自己写的网络服务bind端口是时候,[1024,n]
1.认识URL
平时我们俗称的“网址”,其实就是说的URL
查阅文档
查看视频
上面都是以网页的形式呈现的 .html文件
http——获取网页资源的,视频,音频等也都是文件!!
.html文件就是http是向特定的服务器申请特定的“资源”的,获取到本地,进行展现或者某种使用的!
2.http协议的请求格式和响应格式
样例请求:
GET / HTTP/1.1
Host: 114.55.229.240:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
如何协议的request or response;
报头+有效载荷
1.http如何保证自己的报头:按行读取,直到读取到空行
2.你又如何保证,你能读取到完整的正文?
报头能读取完毕,请求或者响应属性中,一定要包含正文的长度!
3.编写响应 - GET
/不是根目录,web更目录,可以设置为根目录
程序运行在tcp目录下,通过读取请求,将请求路径前加上wwwroot就是正在请求的相对路径了。
std::string getPath(std::string http_request)
{
std::size_t pos = http_request.find(CRLF);
if(pos==std::string::npos)return "";
std::string request_line = http_request.substr(0,pos);
//GET /a/b/c
std::size_t first = request_line.find(SPACE);
if(first == std::string::npos)return "";
std::size_t second = request_line.rfind(SPACE);
if(first==second)return "";
std::string path = request_line.substr(first+SPACE_LEN,second-first-SPACE_LEN);
if(path.size()==1&&path[0]=='/') path+= HOME_PATH;
return path;
}
std::string readFile(const std::string& recource )
{
std::ifstream in (recource);
if(!in.is_open()) return "open error";
std::string content;
std::string line;
while(std::getline(in,line))content+=line;
in.close();
return content;
}
void handlerHttpRequest(int sock, const std::string clientIp, uint16_t clientPort)
{
std::cout<<clientIp<<": "<<clientPort<<std::endl;
char buffer[1024];
ssize_t s = read(sock,buffer,sizeof(buffer));
if(s>0)
{
std::cout<<buffer;
}
std::string path = getPath(buffer);
std::cout<<path<<std::endl;
std::string recource = ROOT_PATH;
recource+=path;
std::string html = readFile(recource);
std::string response;
response = "HTTP/1.0 200 OK\r\n";
response+="Content-Type: text/html\r\n";
response+="Conetent-Length: "+std::to_string(html.size())+"\r\n";
response+="\r\n";
response+=html+CRLF;
send(sock,response.c_str(),response.size(),0);
}
4.报头
我们的网络行为无非有两种:
1.我想把远端的资源拿到你的本地:GET /index.html http/1.1
2.我们想把我们的属性字段,提交到远端 GET or POST
1.GET
注意下面提交方式是用get
<form name="input" action="/a/b/c.html" method="get">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
GET http://114.55.229.240:1025/a/b/c.html?user=zhangsan&passwd=123456 HTTP/1.1
Host: 114.55.229.240:1025
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://114.55.229.240:1025/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
在HTTP中GET会以明文方式将我们对应的参数信息,拼接到url中。
2.POST
<form name="input" action="/a/b/c.html" method="post">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
POST http://114.55.229.240:1025/a/b/c.html HTTP/1.1
Host: 114.55.229.240:1025
Connection: keep-alive
Content-Length: 27
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://114.55.229.240:1025
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://114.55.229.240:1025/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
user=zhangsan&passwd=123456
POST方式提交参数,会将参数以明文的方式,拼接到http的正文中进行提交!
3.GETvsPOST
1.GET通过url传参
2.POST通过正文传承
3.GET方式传参不私密
4.POST方法因为通过正文传参数,所以,相对比较私密一些
5.GET通过url传参,POST通过正文传参,所以一般一些比较大的内容都是通过POST传参的
4.HTTP报头
常见Header:
Content-Type: 数据类型(text/html等)
Content-Length: Body的长度
Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
User-Agent: 声明用户的操作系统和浏览器版本信息;
Referer: 当前页面是从哪个页面跳转过来的;
Location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
HTTP的状态码:
5.状态码3xx
301:永久重定向
302:临时重定向
void handlerHttpRequest(int sock, const std::string clientIp, uint16_t clientPort)
{
std::cout << clientIp << ": " << clientPort << std::endl;
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer));
if (s > 0)
{
std::cout << buffer;
}
std::string response = "HTTP/1.1 302 临时重定向\r\n";
response += "Location: https://www.baidu.com\r\n";
response += "\r\n";
send(sock, response.c_str(), response.size(), 0);
}
访问自己的网站就跳转到百度搜索。
6.cookie策略
http协议特点之一:无状态
用户需要:用户保持
一旦登陆,会有各种会哈保持策略
这个策略就是——cookie策略
服务器返回响应,报头添加:
Set-Cookie: 内容
void handlerHttpRequest(int sock, const std::string clientIp, uint16_t clientPort)
{
std::cout << clientIp << ": " << clientPort << std::endl;
char buffer[1024];
ssize_t s = read(sock, buffer, sizeof(buffer));
if (s > 0)
{
std::cout << buffer;
}
test1
std::string path = getPath(buffer);
std::cout<<path<<std::endl;
std::string recource = ROOT_PATH;
recource+=path;
std::string html = readFile(recource);
std::string response;
response = "HTTP/1.0 200 OK\r\n";
response+="Content-Type: text/html\r\n";
response+="Conetent-Length: "+std::to_string(html.size())+"\r\n";
response+="Set-Cookie: this is my cookie content;\r\n";
response+="\r\n";
response+=html;
send(sock,response.c_str(),response.size(),0);
///test1_end
}
下一次浏览器发送请求,就会在报头添加Cookie
POST http://114.55.229.240:1026/a/b/c.html HTTP/1.1
Host: 114.55.229.240:1026
Connection: keep-alive
Content-Length: 20
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://114.55.229.240:1026
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36 Edg/123.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://114.55.229.240:1026/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: this is my cookie content; this is my cookie content
user=111&passwd=2222
方案二:cookie+session
7.Connection
Connection:keep-alive
Connection:closed
用户所看到的完整的网页内容——背后可能是无数次http请求
http底层主流采用的就是tcp协议!
三、HTTPS协议
1.什么是https
HTTPS也是⼀个应⽤层协议.是在HTTP协议的基础上引⼊了⼀个加密层.
HTTP协议内容都是按照⽂本的⽅式明⽂传输的.这就导致在传输过程中出现⼀些被篡改的情况
http提供的服务端口是:80
https提供的服务端口是:443
所有的加密,都是为了防止中间人篡改。
2.什么是加密
加密就是把明⽂要传输的信息)进⾏⼀系列变换,⽣成密⽂.
解密就是把密⽂再进⾏⼀系列变换,还原成明⽂.
在这个加密和解密的过程中,往往需要⼀个或者多个中间的数据,辅助进⾏这个过程,这样的数据称为密钥
加密解密到如今已经发展成⼀个独⽴的学科:密码学.
⽽密码学的奠基⼈,也正是计算机科学的祖师爷之⼀,艾伦·⻨席森·图灵
3.为什么要加密
臭名昭著的"运营商劫持"
因为http的内容是明⽂传输的,明⽂数据会经过路由器、wifi热点、通信服务运营商、代理服务器等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双⽅察觉,这就是 中间⼈攻击 ,所以我们才需要对信息进⾏加密。
4.常见的加密方式
1.对称加密
- 采用单钥密码系统的加密方法,同一个秘钥可以同时用作信息的加密和解密,这种方法称为对称加密,也称为单秘钥教秘,特性:加密和解密所用的秘钥是相同的。
- 常见对称加密算法:DES,3DES,AES,TDEA,Blowfish,RC2等
- 特点:算法公开、计算量⼩、加密速度快、加密效率⾼
对称加密其实就是通过同⼀个"密钥",把明⽂加密成密⽂,并且也能把密⽂解密成明⽂
2.非对称加密
- 需要两个密钥来进⾏加密和解密,这两个密钥是公开密钥(publickey,简称公钥)和私有密钥(privatekey,简称私钥)。
- 常⻅⾮对称加密算法(了解):RSA,DSA,ECDSA
- 特点:算法强度复杂、安全性依赖于算法与密钥但是由于其算法复杂,⽽使得加密解密速度没有对称加密解密的速度快。
⾮对称加密要⽤到两个密钥,⼀个叫做"公钥",⼀个叫做"私钥".
公钥和私钥是配对的.最⼤的缺点就是运算速度⾮常慢,⽐对称加密要慢很多.
- 通过公钥对明⽂加密,变成密⽂
- 通过私钥对密⽂解密,变成明⽂
5.数据摘要&&数据指纹
- 字指纹(数据摘要),其基本原理是利⽤单向散列函数(Hash函数)对信息进⾏运算,⽣成⼀串固定⻓度的数字摘要。数字指纹并不是⼀种加密机制,但可以⽤来判断数据有没有被窜改。
- 摘要常⻅算法:有MD5、SHA1、SHA256、SHA512等,算法把⽆限的映射成有限,因此可能会有碰撞(两个不同的信息,算出的摘要相同,但是概率⾮常低)
- 摘要特征:和加密算法的区别是,摘要严格意义不是加密,因为没有解密,只不过从摘要很难反推原信息,通常⽤来进⾏数据对⽐
6.总结
HTTPS⼯作过程中涉及到的密钥有三组.
- 第⼀组(⾮对称加密):⽤于校验证书是否被篡改.服务器持有私钥(私钥在形成CSR⽂件与申请证书时获得),客⼾端持有公钥(操作系统包含了可信任的CA认证机构有哪些,同时持有对应的公钥).服务器在客⼾端请求是,返回携带签名的证书.客⼾端通过这个公钥进⾏证书验证,保证证书的合法性,进⼀步保证证书中携带的服务端公钥权威性。
- 第⼆组(⾮对称加密):⽤于协商⽣成对称加密的密钥.客⼾端⽤收到的CA证书中的公钥(是可被信任的)给随机⽣成的对称加密的密钥加密,传输给服务器,服务器通过私钥解密获取到对称加密密钥.
- 第三组(对称加密):客⼾端和服务器后续传输的数据都通过这个对称密钥加密解密.
其实⼀切的关键都是围绕这个对称加密的密钥.其他的机制都是辅助这个密钥⼯作的.
- 第⼆组⾮对称加密的密钥是为了让客⼾端把这个对称密钥传给服务器.
- 第⼀组⾮对称加密的密钥是为了让客⼾端拿到第⼆组⾮对称加密的公钥
四、传输层
1.UDP
1.协议端格式
2.UDP特点
UDP传输的过程类似于寄信。
- 无连接:知道对端IP和端口号就直接进行传输,不需要建立连接
- 不可靠:没有确认机制,没有重传机制,如果因为网络故障该段无法发送到对方,UDP协议也不会给应用层返回任何错误信息
- 面向数据报:不够灵活的控制读写数据的次数和数量
3.UDP缓冲区
- UPD没有真正意义上的发送缓冲区,调用sendto会直接交给内核,由内核数据传给网络协议进行后续的传输动作。
- UPD具有接受缓冲区,但是这个缓冲区不能保证到的UDP报的顺序和发送UPD报的顺序一致,如果缓冲区满了,再到达UDP数据就会被丢弃。
UDP的socket既能读,也能写,这个概念叫做全双工。
4.UDP使用注意事项
- 我们注意到,UDP协议首部中有一个16位的最大长度,也就是说一个UDP能传输的数据最大长度是64KB(包含UPD首部)
- 然后64KB在当今的互联网环境下,是一个非常小的数字。
- 如果我们需要传输的数据超过64KB,就需要在应用层手动分包,多次发送,并在接收端手动拼装。
2.TCP
TCP全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制;
1.TCP协议段格式
- 源/目的端口号:表示数据是从哪一个进程来,到哪一个进程去;
- 32位序号/32位确认号:下面说明
- 4位首部长度:表示该TCP头部有多少个32bit(有多少个4字节),所以TCP头部最大长度是15*4=60,20是标志长度,因为选项可能报头会变长。
- 6位标志位:下面说明
- 16位窗口大小:下面说明
- 16位校验和: 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也包含TCP数据部分.
- 16位紧急指针: 标识哪部分数据是紧急数据
- 40字节头部选项: 暂时忽略
2.确认应答(ACK)机制 - 32位序号/32位确认序号
可靠性问题:
1.什么是不可靠:丢包,乱序,校验失败
2.怎么确认一个报文是丢了还是没丢?
-
我们发的出去的消息,我们如何得知对方是否收到,只要得到应答就意味着我刚刚发的消息100%收到了。
-
在长距离交换的时候,永远有一条最新的数据是没有应答的!但是,只要发送消息有对方的应答,我们就认为我们发送的消息,对方是收到的。
-
我们如果收到了应答,我们确认是没丢,否则就是不确定。这个机制就是确认应答机制。
建立一个共识:tcp进行通信的时候,绝对不要忘记,发送出去的报文一定会携带tcp报头。
为什么TCP要有两组序号?
因为TCP协议是全双工的,主机B给主机A应答同时给主机B发送消息,也需要序号,保证数据可靠送达,还得确认之前得到主机A发送的消息,就需要两组序号。
3.流量控制 - 16位窗口大小
IO类函数,本质其实都是拷贝函数。
-
如果发送方发的太快了,导致接受方来不及接受,如何解决?
需要让发送方知道接受方的接受能力! -
接受方的接受能力,哪一个指标表示?
接受方的接受缓冲区的剩余空间的大小决定。 -
发送方如何知道接受方的接受能力?
发送方会得到接受方的应答,而应答本质就是包含TCP报头,tcp报头可以有保存接受方接受能力的属性。 -
报头中填充的是自己的还是对方的?
自己的,因为报文是发送给对方的
4.6个标记位
为什么有这6个标记位?
报文也是有类别的。
- SYN:只要报文是建立链接的请求,SYN需要被设置为1,称为同步报文段
- FIN:该报文是一个断开链接的请求报文,称为结束报文段
- ACK:确认标记位,表示对历史报文的确认(不仅仅),一般在大部分正式通信的情况下,ACK都是1。
- PSH:提示接受端应用程序立刻从TCP缓冲区把数据读走
如果接受端接受缓冲区满了,应用层很忙,一直没取数据,发送端就可以PSH设置为1,催促接受端把数据读走。
-URG:紧急指针标记位
报文在发送的时候,是可能乱序到达的!——是不可靠的一种,需要让我们的报文进行按序到达——如何做到?根据32位序号排队
如果数据是必须在TCP中进行按序到达的话,也就是说如果有一些数据优先级更高,但是序号较晚,无法做到数据被优先紧急处理!
在识别到紧急标记位,就会根据16紧急指针读取数据中的一个字节。 - RST:对方要求重新建立连接,我们把携带RST标识的称为复位报文段
tcp是面向连接的,如何理解连接?大量的链接,os就需要管理这些链接,如何管理?
先描述,再组织。三次握手成功,就需要花时间,花空间,来管理链接,是有成本的。
三次握手一定能成功吗?为什么不能一次握手?两次握手?为什么是三次握手?
不一定。如果一次握手,客服端可以通过发送大量请求(恶意),服务器就需要维护链接,而导致资源被占满。和一次握手类似。如果是恶意请求,客户端也要维护链接,如果最后一次应答没有到,主动权在服务端(嫁接链接成本给客户端),可以决定是否维护链接,也是以最小成本方式验证全双。
如果最后一次ACK没有到达服务端,此时客户端认为建立好连接,服务器认为没有建立好连接,却收到了客户端的数据时,服务端就意识到和客户端建立失败,要告诉客户端关闭连接重新连接,将报文RST设置为1。
5.超时重传
- 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B;
- 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发
但是, 主机A未收到B发来的确认应答, 也可能是因为ACK丢失了
因此主机B会收到很多重复数据. 那么TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉.这时候我们可以利用前面提到的序列号, 就可以很容易做到去重的效果.
-
那么, 如果超时的时间如何确定?
最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.
但是这个时间的长短, 随着网络环境的不同, 是有差异的.
如果超时时间设的太长, 会影响整体的重传效率;
如果超时时间设的太短, 有可能会频繁发送重复的包; -
TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间
Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2500ms 后再进行重传.
如果仍然得不到应答, 等待 4500ms 进行重传. 依次类推, 以指数形式递增.
累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接
6.三次握手和四次挥手
listen的第二个参数,叫做底层的全连接队列的长度,算法是:n+1,表示在不accept的情况下你最多能够维护多少个连接,再来一个就会无法完成三次握手,多来的只能维护在SYN_RCVD。
四次挥手时,主动断开连接的一方,会维护连接一个状态TIME_WAIT,进程挂了,但端口号被占用着,如果想重启服务bind会显示端口占用,需要等待TIME_WAIT结束。解决TIME_WAIT状态引起的bind失败的方法:
使用setsockopt()设置socket描述符的 选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符
int opt =1;
setsockopt(listsock_,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
setsockopt(listsock_,SOL_SOCKET,SO_REUSEPORT,&opt,sizeof(opt));
7.滑动窗口
1.什么是滑动窗口
发送出去的数据,在没有得到应答(1.发送成功2.发送失败)的情况下,必须被保留,以便于支持超时重传。保留的数据在发送缓冲区中。将发送缓冲区划分如下:
对于已经发送,但没有得到响应的区域就是滑动窗口。
扩展:
-
如何理解缓冲区和滑动窗口?
缓冲区就是个char sendBuffer[16*1024]
滑动窗口就是:
int start_index
int end_index
所谓的滑动就是++ -
tcp的发送缓冲区其实是被设计成为环状结构的!
-
滑动窗口大小由谁决定?
目前:是由对方的接受的能力决定!是发送方收到接受方的TCP数据报头中的窗口大小 -
滑动窗口一定会向右移动吗?滑动窗口可以变大吗?可以变小吗?
没发之前接受能力是4KB,发送之后,对方没有读取,返回应答的报头接受能力小于4KB,滑动窗口的end_index就不动,start_index++。所以滑动窗口不一定向右移动,且滑动窗口可能会减小,也可能增大。
2.滑动窗口如何变化
情况一:数据包已经抵达, ACK被丢了
这种情况下, 部分ACK丢了并不要紧, 因为可以通过后续的ACK进行确认
情况二: 数据包就直接丢了
- 当某一段报文段丢失之后, 发送端会一直收到 1001 这样的ACK, 就像是在提醒发送端 "我想要的是 1001"一样;
- 如果发送端主机连续三次收到了同样一个 “1001” 这样的应答, 就会将对应的数据 1001 - 2000 重新发送;
- 这个时候接收端收到了 1001 之后, 再次返回的ACK就是7001了(因为2001 - 7000)接收端其实之前就已经收到了, 被放到了接收端操作系统内核的接收缓冲区中
这种机制被称为 “高速重发控制”(也叫 “快重传”)
8.流量控制
流量控制接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
- 窗口大小字段越大, 说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
- 发送端接受到这个窗口之后, 就会减慢自己的发送速度;
- 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端.
接收端如何把窗口大小告诉发送端呢? 回忆我们的TCP首部中, 有一个16位窗口字段, 就是存放了窗口大小信息;
那么问题来了, 16位数字最大表示65535, 那么TCP窗口最大就是65535字节么?
实际上, TCP首部40字节选项中还包含了一个窗口扩大因子M, 实际窗口大小是 窗口字段的值左移 M 位
9.拥塞控制
虽然TCP有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.
因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据,是很有可能引起雪上加霜的
TCP引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
-
此处引入一个概念程为拥塞窗口
-
发送开始的时候, 定义拥塞窗口大小为1;
-
每次收到一个ACK应答, 拥塞窗口加1;
-
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口;
像上面这样的拥塞窗口增长速度, 是指数级别的. “慢启动” 只是指初使时慢, 但是增长速度非常快. -
为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.
-
此处引入一个叫做慢启动的阈值
-
当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长
-
当TCP开始启动的时候, 慢启动阈值等于窗口最大值;
-
在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1