zoukankan      html  css  js  c++  java
  • TCP连接建立与释放

    tcp建立连接

    tcp连接的建立需要经历”三次握手“的过程。过程如下

    1. client发送SYN包(值为j)以及SEQ包到server端,此时client进入SYN_SEND状态。此为第一次握手。
    2. server端收到SYN包后,发送一个ACK(值为seq+1)确认包和SYN(值为k)给client,此时server进入SYN_RECV状态。此为第二次握手。
    3. client收到SYN+ACK包后,向server发送一个ACK(值为k+1),该包发送完成后,client和server均进入ESTABLISH状态。此为第三次握手。

    client和server两端状态变化如下:

    client:
    CLOSED->SYN_SEND->ESTABLISH
    server:
    CLOSED->LISTEN->SYN_RECV->ESTABLISH

    为什么是3次握手,不是2次或者4次呢?

    建立一个可靠的tcp连接,至少是要3次的,既然3次就足够,为什么还要4次。
    那2次可以么。假设2次就建立连接,那么会出现如下情况。
    client发送一个A请求,由于网络原因,没有那么快送达server端。此时client便废弃该请求,重新发起请求B,请求B成功送达server端,并得到响应。
    如果此时便建立连接,若之前的请求A终于到达server端了,由于请求的格式都是正常的,于是也发送响应回去client,而client则认为该请求已经被废弃。于是server端就一直挂着这个请求,造成资源浪费。

    因此,才需要让client再发送一次确认给server端,server接收确认后才建立连接,便可以避免这种情况了。

    tcp连接释放

    Tcp释放连接的过程需要经历“四次挥手”的过程,为什么建立连接只需要3次握手,而释放连接需要进行4次挥手呢?

    很简单,因为TCP连接是全双工(Full Duplex)的,因此造成了两个方向都需要进行关闭。

    怎么理解呢?

    client和server,需要关闭连接,此时client通知server我要关闭连接了,此时关闭的只会是client这一端的连接,而server端并未关闭,它依旧能够向client发送数据。

    当然,关闭连接也可以是server作为主动方的。

    接下来以client主动断开与server端的连接为场景来描述整个过程,我们把它分为两个阶段,分别为client端关闭连接和server端关闭连接。

    先上图
    tcp建立连接和释放连接

    第一阶段

    1. 首先client会发送一个FIN包给server(同时还有ack和seq包),这是要告诉server,我已经没有数据要发给你了,此时client处于FIN_WAIT_1状态。接收到FIN包的server处于CLOSE_WAIT的状态。
    2. server发回一个ACK(值为client传过来的seq+1)和seq(值为client传过来的ack的值)给client。client收到server发过来的包后确认关闭连接,此时client处于FIN_WAIT_2。

    第二阶段

    1. server在接收到client的FIN后,得知client要断开tcp连接了,于是在发送完ack和seq给client后,自己发送一个FIN包给client(也带有ack和seq包),告诉client我也要断开连接了,此时server处于LAST_ACK状态。
    2. client接收到server的FIN信息后,会回复server一个ack包,并且会进入TIME_WAIT状态,持续2个MSL(Max Segment Lifetime),这个时间windows下为240s。而server接收到client后便关闭连接。client在指定时间过后仍然没有接收到server的数据,确认server已经没有数据过来,也关闭了连接。

    此时双方都进入CLOSED状态。

    分析一下

    server在接收到client的FIN后,会返回ACK给client,此时关闭的就是读通道,也就是说不能再从这个连接中读信息了。

    client收到server发送过来的对自己的FIN的确认包ack后,便关闭了写通道,不能再向连接中写信息了。

    server也开始关闭连接,发送FIN给client,而client收到后便回复ack给server,同时关闭读通道,自己则进入TIME_WAIT状态。

    server收到client发送过来的对自己的FIN的确认包ack后,便关闭了写通道,状态转化为CLOSED。

    而client在TIME_WAIT结束后也进入CLOSED状态。

    因此client和server会经历如下状态的转移
    client:
    FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
    server:
    CLOSE_WAIT->LAST_ACK->CLOSED

    其中有几个关键点需要注意:

    1. 这个TIME_WAIT的作用是什么?
      这个博客是这么解释的。

      原因有二:
      一、保证TCP协议的全双工连接能够可靠关闭
      二、保证这次连接的重复数据段从网络中消失
      
      先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN。  
      此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。  
      这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。  
      所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。
      
      再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。  
      一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair。  
      于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。  
      
    2. CLOSE_WAIT的解释。
      在以上事例,我们知道server在接收到FIN后,发送ACK之前会进入CLOSE_WAIT,如果长期处于这个状态,或者说服务器出现大量CLOSE_WAIT,说明ACK包一直没有发出,这时候就应该检查代码了。

    3. TIME_WAIT注意事项
      从事例我们知道,主动关闭连接的一方会经历TIME_WAIT状态,在该状态下的socket是不会被回收的。而如果是服务器端主动关闭连接,则可能会面临处于大量TIME_WAIT的情况(因为连接很多嘛),会严重影响服务器的处理能力。
      怎么解决呢,那就减少服务器端TIME_WAIT的时间咯。

    综上所述,client和server从建立连接到断开连接,整个状态的变化如下:

    client:
    CLOSED->SYN_SEND->ESTABLISH->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
    server:
    CLOSED->LISTEN->SYN_RECV->ESTABLISH->CLOSE_WAIT->LAST_ACK->CLOSED

    websocket

    tcp是传输层的协议,tcp三次握手后,应用层协议http也便建立了连接。而对于当今web的发展情况,http仍有许多瓶颈。

    1. 一条连接只能发送一个请求。
    2. 请求只能从客户端开始。客户端不可以接收除响应以外的指令。
    3. 请求/响应首部未经压缩发送,首部信息越多延迟越大。
    4. 发送冗长的首部。每次互相发送相同的首部造成较多的浪费。
    5. 可任意选择数据压缩格式。非强制压缩发送。

    虽然已经出现了很多解决方案,如ajax、comet,但是他们最终使用的都是http协议,因此也无法从根本上解决这些瓶颈。
    因此也就诞生了一个新的通信协议,WebSocket协议,一种全双工通信协议。

    该通信协议建立在http协议的基础之上,因此连接的发起方仍然是客户端,在http连接建立之后,再将协议升级为webSocket连接,在webSocket连接建立之后,客户度和服务器端都可以主动向对方发送报文信息了。

    建立webSocket连接,需要先建立http连接,并在此基础上再进行一次”握手“。

    client会发起一个”握手“的请求,请求首部含有upgrade:websocket(还有其他首部,具体看如下示例)。服务器端返回一个101状态码,确认转换协议。完成握手后便可以使用websocket协议进行通信。

    Client(request)

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: AQIDBAUGBwgJCgsMDQ4PEC==
    Origin: http://example.com
    Sec-WebSocket-protocol: chat, superchat
    Sec-WebSocket-Version: 13
    

    Sec-WebSocket-Key其值采用base64编码的随机16字节长的字符序列,服务器端根据该域来判断client确实是websocket请求而不是冒充的,如http
    Sec-WebSocket-protocol使用的子协议
    Sec-WebSocket-Version该值必须是13

    Server(response)

    HTTP/1.1 101 switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Protocol: chat
    

    Sec-WebSocket-Accept值是由client请求首部中的Sec-WebSocket-Key做SHA-1 hash计算,然后再把得到的结果通过base64加密而来

    连接建立后,通信的url格式如下:
    ws://example.com/
    wss://example.com/

    参考资料

      1. TCP的三次握手(建立连接)和四次挥手(关闭连接)
      2. 服务器TIME_WAIT和CLOSE_WAIT详解和解决办法
  • 相关阅读:
    cookie和session及token的区别联系
    linux/Deepin /Debian 9 Stretch安装Wine
    Deepin 15.9系统直接运行exe运行程序
    cookie和session及token的区别联系
    Deepin 15.9系统直接运行exe运行程序
    deepin系统右键刷新解决增删改文件没有变化
    linux/Deepin /Debian 9 Stretch安装Wine
    deepin系统右键刷新解决增删改文件没有变化
    Linux系统中的截图功能(类似QQ、微信、Snipaste截图功能)
    Linux系统中的截图功能(类似QQ、微信、Snipaste截图功能)
  • 原文地址:https://www.cnblogs.com/wajika/p/6657353.html
Copyright © 2011-2022 走看看