跳转至

Python 模拟一个CLOSE_WAIT

TIME_WAIT状态的存在有两个理由:

  1. 让4次握手关闭流程更加可靠;4次握手的最后一个ACK是是由主动关闭方发送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次发送出去。
  2. 防止lost duplicate对后续新建正常链接的传输造成破坏。lost duplicate在实际的网络中非常常见,经常是由于路由器产生故障,路径无法收敛,导致一个packet在路由器A,B,C之间做类似死循环的跳转。IP头部有个TTL,限制了一个包在网络中的最大跳数,因此这个包有两种命运,要么最后TTL变为0,在网络中消失;要么TTL在变为0之前路由器路径收敛,它凭借剩余的TTL跳数终于到达目的地。但非常可惜的是TCP通过超时重传机制在早些时候发送了一个跟它一模一样的包,并先于它达到了目的地,因此它的命运也就注定被TCP协议栈抛弃。另外一个概念叫做incarnation connection,指跟上次的socket pair一摸一样的新连接,叫做incarnation of previous connection。lost duplicate加上incarnation connection,则会对我们的传输造成致命的错误。大家都知道TCP是流式的,所有包到达的顺序是不一致的,依靠序列号由TCP协议栈做顺序的拼接;假设一个incarnation connection这时收到的seq=1000, 来了一个lost duplicate为seq=1000, len=1000, 则tcp认为这个lost duplicate合法,并存放入了receive buffer,导致传输出现错误。通过一个2MSL TIME_WAIT状态,确保所有的lost duplicate都会消失掉,避免对新连接造成错误。

如果机器上有time_wait ,调优后还是有,说明机器资源紧张,应该进行升级配置,或者代码本身问题

• CLOSED: 关闭状态,没有连接活动

• LISTEN: 监听状态,服务器正在等待连接进入

• SYN_SENT: 已经发出连接请求,等待确认

• SYN_RCVD: 收到一个连接请求,尚未确认

• ESTABLISHED: 连接建立,正常数据传输状态

• FIN_WAIT_1:(主动关闭)已经发送关闭请求,等待确认

• FIN_WAIT_2:(主动关闭)收到对方关闭确认,等待对方关闭请求

• CLOSE_WAIT:(被动关闭)收到对方关闭请求,已经确认

• LAST_ACK:(被动关闭)等待最后一个关闭确认,并等待所有分组死掉

• TIMED_WAIT: 完成双向关闭,等待所有分组死掉

• CLOSING: 双方同时尝试关闭,等待对方确认

从图上讲,客户端主动关闭连接,此时服务器就会出现close_wait

按照上图所示,我来用Python写个http请求。 文件名为 my_flask.py

# -*- coding: utf-8 -*
from flask import Flask, request
import datetime
import sys
import time
reload(sys)

sys.setdefaultencoding("utf-8")

ENV = 'development'
DEBUG = True
SECRET_KEY = 'development key'

app = Flask(__name__)
app.config.from_object(__name__)
app.app_context().push()

app.permanent_session_lifetime = datetime.timedelta(seconds=10 * 60)


# 设置连接时长
@app.route('/mypost', methods=['GET', 'POST'])
def mypost():
    if request.method == 'POST':
        temp = request.json.get('data')
        print(temp)
        return "success post"

@app.route('/myget', methods=['GET', 'POST'])
def myget():
    if request.method == 'GET':
        time.sleep(100)
        return "success get"

@app.route('/myget1', methods=['GET', 'POST'])
def myget_one():
    if request.method == 'GET':

        return "success get"

if __name__ == '__main__':
    # 在服务器上,这么启动
    # app.run(host='0.0.0.0')
    app.run(host='0.0.0.0',threaded=True)

上面的代码 myget里面我写了睡100秒,来模拟复杂的逻辑造成接口返回数据等待时间很长,如下图启动

然后再客户端上执行curl命令,模拟客户端请求。

发现客户端已经和服务端建立了链接

变化太快,截图不到,建立链接这张图和下面两个FIN_WAIT分别属于两次实验

curl命令然后Ctrl+c掉,客户端的链接变成了FIN_WAIT1

然后变成了

从服务端上截图上看

上面是因为服务端还没处理完,服务端迟迟不给客户端返回FIN+ACK包,所以服务端会呈现出close_wait

还有种情况是,客户端和服务端建立连接后,客户端给服务端发送数据,数据会被放入 accept queue (全连接队列),等待应用 accept(接收),如果应用迟迟没有从accept queue里面去 accept 连接,等到 client 超时时间,主动关闭了连接,这时连接在 server 端仍在全连接队列中,状态变为 CLOSE_WAIT

补充

curl --connect-timeout 10 -m 20 "http://XXXXXXX"

--connect-timeout 为允许连接到服务器的最长时间,就是网络建立连接的过程的最长时间 连接超时的话,出错提示形如: curl: (28) connect() timed out!

-m 允许整个操作所用的最长时间,就是curl命令的执行时间 出错提示如:curl: (28) Operation timed out after 2000 milliseconds with 0 bytes received

客户端执行

curl -m 1  http://192.168.80.128:5000/myget || netstat -tanp