文章目录
1.链接管理机制
1.1为什么需要三次握手
1.一、二次握手容易受到SYN洪水攻击
2.tcp是全双工的,用最小的成本验证全双工
第一次客户端向服务端发送SYN,第二次服务器向客户端发送SYN+ACK,此时只验证了客户端是可以向客户端发送请求和接收数据的,而服务器端并不能确定是否可以向客户端发送消息,当客户端回应ACK(第三次握手),才验证了双方是全双工的
3.让服务器不要出现链接误判的情况,减少服务器的资源的浪费
1.2为什么需要四次挥手
tcp通信是全双工的,断开连接的时候,客户端和服务端都需要断开连接,即双方都需要向对方发送断开连接的请求,并且从对方接受确认应答
1.CLOSE_WAIT
如果服务端存在大量的CLOSE_WAIT说明服务端的上层没有调用close即客户端发送FIN并且收到服务端的ACK应答之后,服务端由于没有调用close,因此并不会向客户端发送FIN,即在内部会存在大量的CLOSE_WAIT状态
因此当我们检查服务器发现存在大量的CLOSE_WAIT状态时,就需要检查上层代码,是否调用了close关闭服务端的链接
2.TIME_WAIT
为什么TIME_WAIT的时间是2MSL?
MSL是TCP报文最大的生存时间,因此TIME_WAIT持续存在2MSL,就能够保证在两个传输方向上,的尚未被接收或者迟到接收的报文段都已经消失(否则服务器重启,可能会收到来自上一个进程的迟到的数据,这种数据可能是错误的)
存在的意义:
1.尽量保证最后一个ACK被对方收到,进而,尽快的释放服务器的资源
最后一次ACK是有可能丢掉的,由于TCP有超时重传机制,丢掉之后,服务端会继续向客户端发送FIN,如果最后ACK
始终丢掉,由于超时重传机制,服务端会认为对端主机出现异常,强制关闭连接
2.等待历史数据从网络消散
比如,客户端给服务器发送:你好、再见(FIN)! 但是FIN先到达的服务端,此时服务端会进行应答,开始进行断开连接流程
而TIME_WAIT状态会等待2MSL的时间(MSL数据单向最大传送时间)。由于TIME_WAIT的存在,就可以接收到遗留的信息
3.CLOSED
只有当双方都变成CLOSED状态,才代表双方已经达成共识,这时,双方才会真正意义上的释放链接对应的资源,不再能够进行通信
2.实验验证CLOSE_WAIT状态和TIME_WAIT状态
实验代码:
使服务端不会调用close
#include <iostream>
using namespace std;
#include <string>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
class Server
{
private:
int port;
int lsock;
public:
Server(int _port)
:port(_port)
,lsock(-1)
{}
void InitServer()
{
signal(SIGCHLD,SIG_IGN);
lsock=socket(AF_INET,SOCK_STREAM,0);
if(lsock < 0)
{
cerr<<"sock error"<<endl;
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=INADDR_ANY;
if(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)
{
cerr<<"bind error"<<endl;
exit(3);
}
if(listen(lsock,5) < 0)
{
cerr<<"listen error"<<endl;
}
}
void EchoHttp(int sock)
{
char request[1024];
size_t s=recv(sock,request,sizeof(request)-1,0);//假设可以读完报文(字节流)
if(s>0)
{
request[s]=0;
cout<<request<<endl;//http全是文本,不做处理直接输出
string response="HTTP/1.0 303 FOUND \r\n";//响应首行
response+= "Content-type: text/html\r\n"; //Content-Type:数据类型(text/html等),响应一个html页面
response+= "location: https://www.baidu.com";
//短链接,游览器根据close信息知晓已经响应完毕,因此客户不写content length
response+="\r\n";
send(sock,response.c_str(),response.size(),0);
}
while(1)//使服务端不会调用close
{
sleep(20);
}
//close(sock);//短链接
}
void start()
{
while(true)
{
sockaddr_in peer;
socklen_t len=sizeof(peer);
int sock=accept(lsock,(struct sockaddr*)&peer,&len);
if(sock < 0)
{
cerr<<"accept error"<<endl;
continue ;
}
cout<<"get a new link"<<endl;
if(fork()==0)
{
close(lsock);
EchoHttp(sock);
exit(0);//短链接
}
while(1)
{
sleep(1);
}
close(sock);
}
}
~Server()
{
if(lsock!=-1)
close(lsock);
}
};
2.1验证CLOSE_WAIT
服务器上出现大量的CLOSE_WAIT状态是因为服务器没有正常调用close,导致四次挥手没有完成,这是应用层的BUG,因此想要解决这个问题,只需要加上close即可
2.2验证TIME_WAIT
解决TIME_WAIT状态引起bind失败的方法
在server的TCP链接没有完全断开之前不允许重新监听,在某些情况下是不合理的
比如我们的服务器,在一个时间点,由于连接的客户端过多,导致了崩溃,这时候服务器作为断开连接的一方,那么此最后会进入TIME_WAIT状态,此时进行服务器重启就会导致再次绑定端口失败(服务器端口一般是固定的),等待的时间就需要很长
这时可以通过下面这个接口来解决这种问题,进行端口的复用(本质上,OS会提前释放掉对应的资源)
3.滑动窗口
3.1滑动窗口是什么
3.2为什么需要滑动窗口
在应答策略之中,发送一次数据段就进行一次应答,收到ACK(串行)后再发送下一个数据段。这样做有一个比较大的缺点就是性能较差,尤其是在数据往返时间较长的时候
滑动窗口就在一定程度上提高了效率,窗口大小指的是无需等待确认应答而可以继续发送数据的最大值,发送窗口内的数据,不需要等待任何ACK,直接发送即可
3.3怎么理解滑动窗口
滑动窗口相当于一个数组,左窗口的下标为left、右窗口的下标为right
缓冲区则相当于一个环形队列,left、right中间间隔就是滑动窗口的大小
3.4 小结
1.当滑动窗口内的数据收到ACK之后,滑动窗口的left指针往后进行移动,right也会随时更新,因为滑动窗口大小是不断的变化的
2.操作系统内核为了维护这个窗口,需要开辟发送缓冲区,记录当前还有哪些数据没有应答,只有应答过的数据才能从缓冲区删除(前面的ACK丢了,但是收到了后面的ACK,也是允许移动的)
3.窗口越大,对方的接收能力越强,效率也就越高
4.高速重发控制(快重传)
利用滑动窗口,一次性发送一批数据,如果出现了丢包,数据如何进行重传?可以分为如下两种情况
情况1:ACK丢了
情况2:数据包丢了
为什么有了快重传还需要超时重传?
这两种重传是互相补充的,比如滑动窗口内只有两组数据,那么最多接收到两个ACK,因此不会触发快重传,此时就需要超时重传了
5.流量控制
5.1什么叫做流量控制
接收端处理数据的能力是有限的,如果一次性传给接收方的数据超过对方窗口大小,就会导致丢包,丢包之后又会进行重发,这就是不必要的资源浪费,因此需要根据对端接收能力来控制发送资源的速度,就叫做流量控制,是发送方通过在自己的TCP报头中填写窗口大小来进行控制的
流量控制更应该属于TCP两大特性中的(安全性、可靠性)可靠性,也可以说效率问题
5.2怎么进行
窗口探测:
对方缓冲区被塞满后,返回来的窗口大小为0,因此滑动窗口大小也为0,此时TCP层会进入阻塞状态
那么什么时候,会进行更新呢?
这时,就需要自己发送报文去询问,是否有空间了
窗口更新通知:
假设窗口探测间隔为1s,但是窗口立马更新好了,此时要等待1s后再进行通信,是很浪费时间的
因此,需要有窗口更新通知,更新好后,立马通知对方
扩张延伸:
上层不关心底层通信:
流量控制是由内核进行的,上层不关心这些,上层只关心缓冲区是否还有容量可以让自己写入数据,并不会关心这些通信细节
分层的意义:
假如现在接收方窗口满了,发送方给对方发送PSH都不取走数据,这就是应用层的BUG,因为协议是分层的,每层都不关心另外一层是怎么样的。
高低水位线:
recv、send都是系统调用,在调用的时候都需要进行身份的切换,在进行切换的时候也是需要消耗时间的
而缓冲区的高低水位线就可以提高这种效率,当数据的内容低于低水位线的时候,TCP就不会去通知上层,高于水位线的时候,才会通知上层一次调用读取大量数据
这种减少用户态-内核态的切换,也是效率提升的一种
6.拥塞控制
6.1是什么
有了TCP的窗口控制,收发主机之间即使不再以一个数据段为单位发送确认应答,也能够连续发送大量数据包。然而,如果在通信刚开始时就发送大量数据,也可能会引发其他问题。
一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。在网络出现拥堵时,如果突然发送一个较大量的数据,极有可能会导致整个网络的瘫痪(大家使用的都是TCP协议,网络状态不好时,大家一起丢包,然后一起进行重发,这样就会在短时间内,导致局部网络阻塞状态加剧)
TCP为了防止该问题的出现,在通信一开始时就会通过一个叫做慢启动的算法得出的数值,对发送数据量进行控制。
为了在发送端调节所要发送数据的量,定义了一个叫做“拥塞窗口”的概念。
6.2怎么判断是网络问题
假如发送1万个数据丢失几个报文,这是正常的丢包现象,如果丢失了一半的报文,这就是网络的问题了
6.3如何控制
流量控制是通信双方控制,因为是发送端根据接收方的能力来决定发送数据的大小
拥塞控制是多人一起控制,在一定的区域内,大家共享网络,并且底层用的都是TCP/IP协议,网络状态不好时,大家一起减少数据的发送,减轻网络的负担