第七十九篇 Flask web_socket示例

一、websocket概要:

websocket是基于TCP传输层协议实现的一种标准协议,用于在客户端和服务端双向传输数据
  传统的客户端想要知道服务端处理进度有两个途径:
  1)通过ajax不断轮询,由于http的无状态性,每次轮询服务器都需要去解析http协议,对服务器压力也很大
  2)采用long poll的方式,服务端不给客户端反馈,客户端就一直等待,服务就一直被挂起,此阶段一直是阻塞状态
  而当服务器完成升级(http–>websocket)后,上面两个问题就得到解决了:
  1)被动性,升级后,服务端可以主动推送消息给客户端,解决了轮询造成的同步延迟问题
  2)升级后,websocket只需要一次http握手,服务端就能一直与客户端保持通信,直到关闭连接,这样就解决了服务器需要反复解析http协议,减少了资源的开销

在这里插入图片描述

二、通过Flask创建websocket服务端

1 群聊服务端

from flask import Flask, request
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
import json

app = Flask(__name__)

user_list=[]

@app.route("/")
def index():
    #请求连接
    user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
    user_list.append(user_socket)
    print(len(user_list),user_list)
    while 1:#循环接收消息
        msg = user_socket.receive()
        for user in user_list:
            if user_socket != user:
                user.send(msg)

if __name__ == '__main__':
    http_serv = WSGIServer(("0.0.0.0", 5000), app, handler_class=WebSocketHandler) #将app实例化到WSGI中,然后运行服务器
    http_serv.serve_forever()

1 群聊客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>群聊页面</h1>
<div id="d1" style="height: 500px;width: 800px;background: burlywood">
    <p id="p1"></p>
</div>
<p>发送内容:<input type="text" id="message"><button onclick="send_msg()">发送消息</button></p>


</body>
</html>
<script type="text/javascript">
    var ws = new WebSocket("ws://127.0.0.1:5000/");
    ws.onmessage = function (res) {
        console.log(res.data);
        var ptag = document.createElement("p");
        ptag.innerText = res.data;
        document.getElementById("d1").appendChild(ptag);
    };

    function send_msg() {
        var msg = document.getElementById("message").value;
        var ptag = document.createElement("p");
        ptag.style.cssText = "text-align: right;";
        ptag.innerText = msg;
        document.getElementById("d1").appendChild(ptag);
        ws.send(msg);
    }

</script>

用户1
在这里插入图片描述
用户2
在这里插入图片描述

2 单聊服务端

from flask import Flask, request, render_template
from geventwebsocket.handler import WebSocketHandler
from gevent.pywsgi import WSGIServer
import json

app = Flask(__name__)

user_dict={}

@app.route("/<username>")
def index(username):
    print(username)#用户名
    user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
    user_dict[username]=user_socket#将用户和对应的socket加入到字典中
    print(len(user_dict),user_dict)
    while 1: #循环接收消息
        msg = user_socket.receive() #接收消息
        msg_dict=json.loads(msg)  #反序列化消息为字典
        to=msg_dict.get("to_user")
        message=msg_dict.get("msg")
        user=user_dict.get(to)
        user.send(message)  #发送给要发送的人

if __name__ == '__main__':
    http_serv = WSGIServer(("0.0.0.0", 5000), app, handler_class=WebSocketHandler)
    http_serv.serve_forever()

2 单聊客户端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>群聊页面</h1>
<div id="d1" style="height: 500px;width: 800px;background: burlywood;">
    <p id="p1"></p>
</div>
<p>您的昵称:<input type="text" id="name">
    <button onclick="openws()">进入聊天室</button>
</p>
<p>给谁:<input type="text" id="to_user"></p>
<p>发送内容:<input type="text" id="message">
    <button onclick="send_msg()">发送消息</button>
</p>


</body>
</html>
<script type="text/javascript">
    var ws = null;

    function openws() {  //通过点击函数连接socket并初始化ws
        var name = document.getElementById("name").value;
        ws = new WebSocket("ws://127.0.0.1:5000/" + name);
        ws.onmessage = function (res) {//接收到消息时触发此函数
            console.log(res.data);
            var ptag = document.createElement("p");//创建p标签
            ptag.innerText = res.data;//为p标签设定值
            document.getElementById("d1").appendChild(ptag); //在div的父标签下加入p标签
        };

    }

    function send_msg() {//发送消息时触发此函数,但必须先让用户进入聊天室
        var msg = document.getElementById("message").value;//获得消息
        var from_user = document.getElementById("name").value;//注册用户名
        var to_user = document.getElementById("to_user").value;//发送给谁
        var ptag = document.createElement("p");//创建p标签
        ptag.style.cssText = "text-align: right;";
        ptag.innerText = msg;
        document.getElementById("d1").appendChild(ptag);//在div下创建p标签,自己发送的消息在右侧
        var msg_obj = {//实例化对象
            msg: msg,
            from_user: from_user,
            to_user: to_user
        };
        console.log(msg_obj);
        ws.send(JSON.stringify(msg_obj));//序列化成字符串
    }

</script>

用户1
在这里插入图片描述
用户2
在这里插入图片描述

三、TCP 连接的“ 三次握手 ”与“ 四次挥手 ”

三次握手

在这里插入图片描述

握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:
(1)首先客户端向服务器端发送一段TCP报文,其中:
标记位为SYN,表示“请求建立新连接”;
序号为Seq=X(X一般为1);
随后客户端进入SYN-SENT阶段。
(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:
标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);
序号为Seq=y;
确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。
(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:
标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
随后客户端进入ESTABLISHED阶段。
服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成。
此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。

“四次挥手”的详解

在这里插入图片描述
(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:
标记位为FIN,表示“请求释放连接“;
序号为Seq=U;
随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。
注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。
(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:
标记位为ACK,表示“接收到客户端发送的释放连接的请求”;
序号为Seq=V;
确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;
随后服务器端开始准备释放服务器端到客户端方向上的连接。
客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段
前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了
(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:
标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。
序号为Seq=W;
确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。
随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。
(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:
标记位为ACK,表示“接收到服务器准备好释放连接的信号”。
序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。
确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。
随后客户端开始在TIME-WAIT阶段等待2MSL

举个栗子:把客户端比作男孩,服务器比作女孩。通过他们的分手来说明“四次挥手”过程。
第一次挥手”:日久见人心,男孩发现女孩变成了自己讨厌的样子,忍无可忍,于是决定分手,随即写了一封信告诉女孩
第二次挥手”:女孩收到信之后,知道了男孩要和自己分手,怒火中烧,心中暗骂:你算什么东西,当初你可不是这个样子的!于是立马给男孩写了一封回信:分手就分手,给我点时间,我要把你的东西整理好,全部还给你!男孩收到女孩的第一封信之后,明白了女孩知道自己要和她分手。随后等待女孩把自己的东西收拾好。
第三次挥手”:过了几天,女孩把男孩送的东西都整理好了,于是再次写信给男孩:你的东西我整理好了,快把它们拿走,从此你我恩断义绝!
第四次挥手”:男孩收到女孩第二封信之后,知道了女孩收拾好东西了,可以正式分手了,于是再次写信告诉女孩:我知道了,这就去拿回来!

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值