flask4 + websocket

2019.7.15

Http协议

Http: 超文本传输协议 传输:Socket TCP 3次握手 断开 四次挥手

无状态请求连接

TCP 3次握手 发起一次请求 - 一次响应 - 连接就断开了

http://www.baidu.com 长时间无请求时,不占用任何资源

劣势:

无状态 - 
比如 : 
客户端1向服务器发起请求 - 服务器确认客户端1的身份 - 处理 - 响应 - 断开
客户端1向服务器发起请求 - 服务器确认客户端1的身份 - 处理 - 响应 - 断开

服务器主动向客户端推送消息

WeChat - 微信     
‘你’发送一条微信 - 给服务器 - ‘你好!’ - 服务器收到消息  - 服务器寻找 ‘我’ - 对着我的连接 (客户端) - 发送 你好!

WeChatPay 
AliPay
1.轮询 对客户端 以及服务器要求最高

客户端轮询 - 由客户端向服务器发起请求 每秒10次左右(QPS 每秒发起请求的次数限制)

场景模拟

传达室 收发快递模型 

1.大爷(接收快递) - 小二 半年之后 扔掉
2.大爷(接收快递) - 小二 -小二从宿舍跑到传达室 
    - 问大爷 有我快递吗?
    -大爷回答:你丫谁呀?  我是大三小二
    - 我知道了,你找我什么事儿? - 小二 : 有我快递吗?  - 大爷:我给你找找,没找着,你回去把,一会过来
    - 小二 :大爷再见 再见 再见 再见
    - 小二到宿舍想起来快递没拿
    - 又去传达室取快递(重复上面的过程)
    
- 很多个小二 同事问大爷快递 
知道很久以后
- 小二 我是大三的小二
- 我知道了,你汇总啊我什么事儿? 
- 小二 : 有我快递吗?
- 大爷: 我这儿真有你的快递
- 小二 拿走快递,飞奔二区
- 小二:大爷再见 再见 再见 再见

​ 小资源访问时 优势很明显 - 因为客户端较少

2.长轮询

服务器长轮询 - 服务器不响应客户端而是将连接暂时保持住 5 秒之后 没有消息,响应客户端

一定时间之后 5s之后 断开连接, 客户端重新发起请求

情景模拟:

大爷(接收快递) - 小二
传达室装修了 - 建立了一个茶室 - 服务升级了
小二到传达室 - 问大爷有我快递吗?  
- 大爷: 你丫谁呀?
- 小二:我是大三小二
- 大爷:我知道了,你找我什么事儿? 
- 小二: 有我快递吗? 
- 大爷:进屋做做,屋里有差,我给你找找快递
- 小二喝茶中。。。。。。
- 大爷:你多等会
- 小二喝茶中。。。。。。
- 大爷:你多等会
- 小二喝茶中。。。。。。
- 小二: 大爷,我去趟厕所
- 断开
- 小二上完厕所,不对呀,我又不是去喝茶的
- 小二来到传达室 loop


小二到传达室 - 问大爷有我快递吗?  
- 大爷: 你丫谁呀?
- 小二:我是大三小二
- 大爷:我知道了,你找我什么事儿? 
- 小二: 有我快递吗? 
- 大爷:进屋做做,屋里有差,我给你找找快递
- 小二喝茶中。。。。。。
- 大爷说 找到了,你的快递
- 小二拿着快递美美的回去了 断开了

优势:

小二不用那么累了 客户端不用大量QPS

劣势:

传达室的茶室空间 严重浪费服务器资源

大爷学会了影分身 开启多线程服务客户端

3.长连接 客户端和 服务器资源 消耗没有那么严重了

连接保持 - Http 发起请求在请求中写一个协议 - WebSocket - 收到请求,自动保持此连接 - 永久不断开, 除非主动断开 - 可以通过此连接主动找到客户端

场景模拟

大爷(接收快递) - 小二
传达室进入信息化时代,装电话了 2万台电话
小二到传达室 - 问大爷有我快递吗?  
- 大爷: 你丫谁呀?
- 小二:我是大三小二
- 大爷:我知道了,你找我什么事儿? 
- 小二: 有我快递吗? 
- 大爷:你把电话号码留下,回去等我电话,接了之后别挂
- 小二回到宿舍证号听见大爷来电话了
- 小二接电话 , 对面,我是你大爷,电话别挂,随时听着,有快递就过来取
- 保持通话

优势:数据实时性

劣势:服务器和客户端需要一个线程来等待消息

玩游戏:一个服务器有资源限制,所以分区,很多个服务器

服务器 完成 IO多路复用

2 . GeventWebsocket + Flask

Web (Http) + Socket (连接保持)

Flask中运行WebSocket - GeventWebsocket

wsgi?

wsgi ---> envir ---> view

wsgi ---> http ---> envir ---> view

wsgi ---> websockethandler ---> envir ---> view

websockethandler 监听了 请求头中的?

ws environ 和 headers

ws请求 environ 原始信息
{'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_SOFTWARE': 'gevent/1.4 Python/3.6', 'SCRIPT_NAME': '', 'wsgi.version': (1, 0), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.errors': <_io.TextIOWrapper name='' mode='w' encoding='UTF-8'>, 'SERVER_NAME': 'Win7-2019WPWAAA', 'SERVER_PORT': '9537', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/my_socket', 'QUERY_STRING': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '50844', 'HTTP_HOST': '127.0.0.1:9537', 'HTTP_CONNECTION': 'Upgrade', 'HTTP_PRAGMA': 'no-cache', 'HTTP_CACHE_CONTROL': 'no-cache', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36', 'HTTP_UPGRADE': 'websocket', 'HTTP_ORIGIN': 'http://localhost:49419', 'HTTP_SEC_WEBSOCKET_VERSION': '13', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'HTTP_COOKIE': 'csrftoken=5L4cgBIxsjanDPXGuTBlOk5ihhrn8LYnSppNIG4LyEclJHsC6MOC7AR9K1jbDJUO; sessionid=ofv3e4j3kmwo26wrimax68wz5t56rmr3', 'HTTP_SEC_WEBSOCKET_KEY': 'KNmnx/BwHQ1R3Q+DxJNRGQ==', 'HTTP_SEC_WEBSOCKET_EXTENSIONS': 'permessage-deflate; client_max_window_bits', 'wsgi.input': , 'wsgi.input_terminated': True, 'wsgi.websocket_version': '13', 'wsgi.websocket': , 'werkzeug.request': }

'wsgi.websocket': ,

http environ
#{'GATEWAY_INTERFACE': 'CGI/1.1', 'SERVER_SOFTWARE': 'gevent/1.4 Python/3.6', 'SCRIPT_NAME': '', 'wsgi.version': (1, 0), 'wsgi.multithread': False, 'wsgi.multiprocess': False, 'wsgi.run_once': False, 'wsgi.url_scheme': 'http', 'wsgi.errors': <_io.TextIOWrapper name='' mode='w' encoding='UTF-8'>, 'SERVER_NAME': 'Win7-2019WPWAAA', 'SERVER_PORT': '9537', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/my_socket', 'QUERY_STRING': '', 'SERVER_PROTOCOL': 'HTTP/1.1', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '50450', 'HTTP_HOST': '127.0.0.1:9537', 'HTTP_CONNECTION': 'keep-alive', 'HTTP_CACHE_CONTROL': 'max-age=0', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_ACCEPT_LANGUAGE': 'zh-CN,zh;q=0.9', 'HTTP_COOKIE': 'csrftoken=5L4cgBIxsjanDPXGuTBlOk5ihhrn8LYnSppNIG4LyEclJHsC6MOC7AR9K1jbDJUO; sessionid=ofv3e4j3kmwo26wrimax68wz5t56rmr3', 'wsgi.input': , 'wsgi.input_terminated': True, 'werkzeug.request': }
http - readers
Host: 127.0.0.1:9537
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: csrftoken=5L4cgBIxsjanDPXGuTBlOk5ihhrn8LYnSppNIG4LyEclJHsC6MOC7AR9K1jbDJUO

ws
WebSocket {url: "ws://127.0.0.1:9537/my_socket", readyState: 3, bufferedAmount: 0, onopen: null, onerror: null, …}

ws - headers
Host: 127.0.0.1:9537
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.108 Safari/537.36
Upgrade: websocket          # == websocket 请求 websockethandler处理的key  握手的信息
Origin: http://localhost:49419
Sec-Websocket-Version: 13   #
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: csrftoken=5L4cgBIxsjanDPXGuTBlOk5ihhrn8LYnSppNIG4LyEclJHsC6MOC7AR9K1jbDJUO; sessionid=ofv3e4j3kmwo26wrimax68wz5t56rmr3
Sec-Websocket-Key: jiytsu3cA3JWnElexaajjA==     # 公钥
Sec-Websocket-Extensions: permessage-deflate; client_max_window_bits

和一个东西计算出私钥 , 匹配出来的值能不能建立起联系

html 代码:



py

# 客户端
# 服务端
# Http  Flask 浏览器
# Websocket GeventWebsocket+Flask 客户端JavaScript(Websocket客户端)

from flask import Flask,request,render_template
from geventwebsocket.handler import WebSocketHandler  # 提供WS协议处理
from geventwebsocket.server import WSGIServer  # 承载服务
from geventwebsocket.websocket import WebSocket  # 语法提示

app = Flask(__name__)

user_socket_list = []

@app.route("/my_socket")
def my_socket():
    # 获取当前客户端与服务器的Socket连接
    user_socket = request.environ.get("wsgi.websocket") # type:WebSocket
    if user_socket:
        user_socket_list.append(user_socket)
        print(len(user_socket_list),user_socket_list)
    # print(user_socket,"OK 连接已经建立好了,接下来发消息吧")
    while 1:
        msg = user_socket.receive()
        print(msg)

        for usocket in user_socket_list:
            try:
                usocket.send(msg)
            except:
                continue

        # user_socket.send(msg)

        # print(request.headers)


@app.route("/gc")
def gc():
    return render_template("gc.html")



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

sl.html




  
  群聊


我的昵称:

发送

py

# 客户端
# 服务端
# Http  Flask 浏览器
# Websocket GeventWebsocket+Flask 客户端JavaScript(Websocket客户端)
import json

from flask import Flask,request,render_template
from geventwebsocket.handler import WebSocketHandler  # 提供WS协议处理
from geventwebsocket.server import WSGIServer  # 承载服务
from geventwebsocket.websocket import WebSocket  # 语法提示

app = Flask(__name__)

# user_socket_dict = {nicheng:lianjie}
user_socket_dict = {}

@app.route("/my_socket/")
def my_socket(username):
    # 获取当前客户端与服务器的Socket连接
    user_socket = request.environ.get("wsgi.websocket") # type:WebSocket
    if user_socket:
        user_socket_dict[username] = user_socket
        print(len(user_socket_dict),user_socket_dict)
    # print(user_socket,"OK 连接已经建立好了,接下来发消息吧")
    while 1:
        msg = user_socket.receive()
        msg_dict = json.loads(msg)
        # msg = user_socket.receive()
        to_user_nick = msg_dict.get("to_user")
        print(to_user_nick)
        to_user_socket = user_socket_dict.get(to_user_nick)
        to_user_socket.send(msg)




@app.route("/sl")
def sl():
    return render_template("sl.html")



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

错误 1 net::ERR_CONNECTION_REFUSED

ws = new WebSocket('ws://192.168.16.40:9537/gc');
WebSocket is already in CLOSING or CLOSED state. 

错误 2

ws = new WebSocket('ws://192.168.16.40:9537/my_socket');
WebSocket {url: "ws://192.168.16.40:9537/my_socket", readyState: 0, bufferedAmount: 0, onopen: null, onerror: null, …}
VM65:1 WebSocket connection to 'ws://192.168.16.40:9537/my_socket' failed: Error in connection establishment: 

发错了地址,发到了 40端口,老师的端口了

gc.html




    
    Title



老 gc .html




    
    Title

    








我的昵称:

ws.onmessage = function (eventMessage) {
eventMessage : 和request似的?

函数作用?

  function send_msg() {
      var content = document.getElementById("content").value;
      ws.send(content);
  }

Uncaught ReferenceError: send_msg is not defined

  var ws = new WebSocket("ws://192.168.16.90:9537/my_socket");
  // 监听电话
  ws.onmessage = function (eventMessage) {
      console.log(eventMessage.data);
      var p = document.createElement("p");
      p.innerText = eventMessage.data;
      document.getElementById("chat_list").appendChild(p);
  };

没有发送函数也可以实现增加信息,只不过服务器端不显示

并不是,好像是没有数据了? 这样 数据是发过去又发回来的,为什么不直接使用呢

for usocket in user_socket_list:
try:
usocket.send(msg)       #前端接收的eventmesaage.data 就是这个msg
# print('haole')
except:
continue

也不知道改动的什么地方,莫名其妙就好了

因为看不出那里有错,照抄的老师的,还把前端后端都换成老师的,然后去看视频,发现老师是没放在群聊的文件夹里,所以就把他提了出来,就好使了,然后再去运行之前的,也好使了。不知道错误在哪?还会出错的,对吗?

告诉你:

不是 from flask import Flask,request,render_template

而是 import json 脑子怎么长的?奇特!

前端写 JSON 大写 额

ws.send(JSON.stringify(sendStr));

laoqunliao




    
    Title

    








我的昵称:

str_obj = JSON.parse(eventMessage.data);
p.innerText = str_obj.from_user +" : "+str_obj.chat;

显示 ----> dasf : adfas

p.innerText = eventMessage.data;

显示 ----> {"from_user":"adas","chat":"ada"}

这个函数果然没大有实际作用,因为通过直接打开html , 错误因为没法识别 tmplates 吗?

# @app.route("/gcs")
# def gc():
#     return render_template("gcs.html")

那个make jinja 了 哪个可以被识别好像 那就可以蓝图文件夹用公有资源了把?!

错误原因?好几次了

ws.send(JSON.stringify(sendStr));

是我没点登陆 ? 发的是空
Uncaught TypeError: Cannot read property 'send' of null

AttributeError: 'NoneType' object has no attribute 'send' # kong不能打.send

Cannot read property 'send' of null

是因为没有用户,不支持离线。亮哥说。解决了,好似,但是我之前怎么可已发过去?也没有确认用户存在所以接收功能吧?

多个人聊天,所以需要另外一个人,不能自己和自己聊吧

源码讲解 :当http直接访问 而不是ws

错误返回:

null not revice()

大意:

websockethandler 也是 WSGI的 只不过封装了

继承的WSGIHandler 除了能够处理标准的http,还可以处理webserver

GUID magic_string 用来做通信协议验证的

self.environ.update ({'wsgi.websocket':None}) #http还是web都有environ原始信息

self.websocket = None #都给了None

只有websocket 才有http_update , http请求没有

读源码的时候各种记笔记: 防止忘了 #

ws登录 ws://192.168.16.90 登录 aaa sec 密钥 。。。

self.websocket = Websocket(self.environ,) #夯住了 新的长连接一直保持着

http -> websocket

environ.update = {''} # http请求进来 没有http-upgrate 只有None , None没法receive

明天讲websocket 握手原理 和 加密 (有时间的话)

源码:
SUPPORTED_VERSIONS = ('13', '8', '7')
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
self.environ.update({
    'wsgi.websocket': None
})
self.websocket = None
upgrade = self.environ.get('HTTP_UPGRADE', '').lower()

'HTTP_UPGRADE': 'websocket'

if upgrade == 'websocket':
    connection = self.environ.get('HTTP_CONNECTION', '').lower()    #加到了connection里面

'HTTP_CONNECTION': 'Upgrade' ws都有 这两个key

    if 'upgrade' not in connection:
        # This is not a websocket request, so we must not handle it
        self.logger.warning("Client didn't ask for a connection "
                            "upgrade")
        return
else:
    # This is not a websocket request, so we must not handle it
    return  
if self.request_version != 'HTTP/1.1':              # websocket 默认是http/1.1  略过

'HTTP_SEC_WEBSOCKET_VERSION': '13', #

if self.environ.get('HTTP_SEC_WEBSOCKET_VERSION'):
    return self.upgrade_connection()

def upgrade_connection(self):

version = self.environ.get("HTTP_SEC_WEBSOCKET_VERSION")

SUPPORTED_VERSIONS = ('13', '8', '7')

为Sec-WebSocket-Accept 做准备

key = self.environ.get("HTTP_SEC_WEBSOCKET_KEY", '').strip()
key_len = len(base64.b64decode(key))

http 请求不就是tcp请求,tcp请求本质上不就是socket请求,所以把 def upgrade_connection(self):

​ #Validate and 'upgrade' the HTTP request to a WebSocket request. ---> self.websocket

如果Sec-WebSocket-Accept匹配对了,一直保持着

self.websocket = WebSocket(self.environ, Stream(self), self)    #创建一个新的连接 保持长连接     # 里面是init函数
self.environ.update({
    'wsgi.websocket_version': version,
    'wsgi.websocket': self.websocket        #把连接放到wsig里了    
})
class WebSocket(object):
    def __init__(self, environ, stream, handler):   #self == WSGIServer? ?WebSocketHandler  
if PY3:
    accept = base64.b64encode(          #accept验证通过的字符串
        hashlib.sha1((key + self.GUID).encode("latin-1")).digest()
    ).decode("latin-1")         #通过之后才能一直连接
else:
    accept = base64.b64encode(hashlib.sha1(key + self.GUID).digest())
headers = [
    ("Upgrade", "websocket"),
    ("Connection", "Upgrade"),
    ("Sec-WebSocket-Accept", accept)    
]

走了这么多,给http请求的返回了none,给ws请求的返回了 websocket(不知道是什么)

程序的部分,为什么会错

http请求,源码里,wsgi.websocket 给的none 然后就返回了

user_socket = request.environ.get("wsgi.websocket") # type:WebSocket    
if user_socket:
    user_socket_list.append(user_socket)
    print(len(user_socket_list),user_socket_list)
# print(user_socket,"OK 连接已经建立好了,接下来发消息吧")
while 1:
    msg = user_socket.receive()     # send_msg 发过来的信息   # http请求这里是None ,
    print(msg)      

所以,AttributeError: 'NoneType' object has no attribute 'receive'

代码整理一下

一个发送框,一直发送,因为while了的,还得跳出来吗? .py
from flask import Flask,request,render_template
from geventwebsocket.handler import WebSocketHandler    #提供WS协议
from geventwebsocket.server import WSGIServer   #承载服务
from geventwebsocket.websocket import WebSocket # 语法提示

app = Flask(__name__)
user_socket_list = []   #存放每个socket的列表

@app.route('/ql')
def ql():
    return render_template('ql1.html')

@app.route('/my_socket')
def my_socket():
    # 获取当前客户端与服务器的socket连接
    user_socket = request.environ.get('wsgi.websocket') #type:WebSocket
    if user_socket:
        user_socket_list.append(user_socket)    # 加入多个用户
        print(len(user_socket_list),user_socket_list)   #1 []
    while 1 :
        msg = user_socket.receive()  # 接收前端发过来的信息
        print(msg)
        for usocket in user_socket_list:
            try:
                usocket.send(msg)
            except :
                continue         #断开连接的 不影响整个群聊

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

一个的前端.html


群昵称.html

我的昵称:

群聊昵称.py
import json

user_socket_dict = {}

@app.route("/my_socket/")
def my_socket(username):
    # 获取当前客户端与服务器的Socket连接
    user_socket = request.environ.get("wsgi.websocket") # type:WebSocket
    if user_socket:
        user_socket_dict[username] = user_socket
        print(len(user_socket_dict),user_socket_dict)
    # print(user_socket,"OK 连接已经建立好了,接下来发消息吧")
    while 1:
        # msg = json.loads(user_socket.receive())
        msg = user_socket.receive()
        print(msg)
        for usocket in user_socket_dict.values():
            print(usocket)
            try:
                usocket.send(msg)
            except:
                continue
私聊.html

我的昵称:

发送

私聊.html
app = Flask(__name__)

# user_socket_dict = {nicheng:lianjie}
user_socket_dict = {}

@app.route("/my_socket/")
def my_socket(username):
    # 获取当前客户端与服务器的Socket连接
    user_socket = request.environ.get("wsgi.websocket") # type:WebSocket
    if user_socket:
        user_socket_dict[username] = user_socket        # bbbb = {gevent
        print(len(user_socket_dict),'dict',user_socket_dict)
    # print(user_socket,"OK 连接已经建立好了,接下来发消息吧")
    while 1:
        msg = user_socket.receive()
        msg_dict = json.loads(msg)
        # msg = user_socket.receive()
        to_user_nick = msg_dict.get("to_user")  #'bbbb'
        print('toueser-nick',to_user_nick)
        to_user_socket = user_socket_dict.get(to_user_nick)     #'bbbb'
        to_user_socket.send(msg)

@app.route("/sl")
def sl():
    return render_template("sl.html")

转载于:https://www.cnblogs.com/Doner/p/11188172.html

你可能感兴趣的:(网络,python,json)