Python 模拟一个CLOSE_WAIT
TIME_WAIT状态的存在有两个理由:
- 让4次握手关闭流程更加可靠;4次握手的最后一个ACK是是由主动关闭方发送出去的,若这个ACK丢失,被动关闭方会再次发一个FIN过来。若主动关闭方能够保持一个2MSL的TIME_WAIT状态,则有更大的机会让丢失的ACK被再次发送出去。
- 防止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