TCP通信细节

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协议,网络状态不好时,大家一起减少数据的发送,减轻网络的负担
在这里插入图片描述

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值