V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
zhishixiang
V2EX  ›  Python

flask socketio 无法向客户端推送消息

  •  
  •   zhishixiang ·
    zhishixiang · 2022-07-13 15:24:14 +08:00 · 2480 次点击
    这是一个创建于 890 天前的主题,其中的信息可能已经有所发展或是发生改变。

    这是客户端:

    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    socketio = SocketIO(app)
    tokenList = {}
    
    
    @app.route("/test")
    def testConnect():
        sid = tokenList["114514"]
        print(sid)
        emit('newMission', {'from': 'server'}, namespace="newMission", to=sid)
        return 'success'
    
    
    @socketio.on('register')
    def register(data):
        print("服务器%s 尝试注册" % data["secret"])
        emit('register', {"token": "1919810"})
        print("注册成功,token 为%s" % "1919810")
        sid = request.sid
        join_room("mainRoom")
        tokenList["114514"] = sid
    
    
    """@socketio.on('message')
    def message(data):
        print(data)  # {'from': 'client'}
        emit('response', {'from': 'server'})
        sleep(5)
        emit('response', "exit")"""
    
    if __name__ == '__main__':
        socketio.run(app, debug=True, host='127.0.0.1', port=8090)
    

    这是服务端:

    import socketio
    
    sio = socketio.Client()
    
    
    @sio.event()
    def connect():
        print('正在注册,请稍后')
        sio.emit('register', {'secret': '114514'})
    
    
    @sio.on('register')
    def isReg(data):
        print("注册成功,当前 token 为" + data["token"])
    
    
    @sio.on("newMission", namespace="message")
    def newMission(data):
        print(data)
    
    
    sio.connect('ws://localhost:8090')
    sio.wait()
    

    预计是打算让服务端获取客户端的 sid ,然后通过 sid 推送消息,但是不知道为什么客户端死活无法收到消息,查了很久也不知道哪里写错了,求助万能的 v 友

    24 条回复    2022-07-15 11:48:07 +08:00
    BeautifulSoup
        1
    BeautifulSoup  
       2022-07-13 15:32:06 +08:00
    请问你现在的部署环境是怎么样的?如果是开发环境,flask 自带的应用服务器是不支持 ws 协议的,需要通过前端 socketio.js 库降级为轮询使用
    zhishixiang
        2
    zhishixiang  
    OP
       2022-07-13 15:36:08 +08:00
    @BeautifulSoup 服务端安装了 gevent-websocket 库,可以使用 websocket ,客户端用的是 python websocket 库
    NessajCN
        3
    NessajCN  
       2022-07-13 16:31:53 +08:00
    服务端不是这么写的
    sio.connect()是客户端连服务器端的函数
    服务器端要用 aiohttp 之类的库部署
    https://python-socketio.readthedocs.io/en/latest/server.html#deployment-strategies
    zhishixiang
        4
    zhishixiang  
    OP
       2022-07-13 16:42:43 +08:00
    @NessajCN 才发现服务端和客户端代码写反了,得换过来看
    NessajCN
        5
    NessajCN  
       2022-07-13 17:21:26 +08:00
    @zhishixiang 那你服务器端那边的 emit 之类的函数要放在 socketio 实例里啊
    socketio.emit()
    zhishixiang
        6
    zhishixiang  
    OP
       2022-07-13 17:50:14 +08:00
    @NessajCN 已经导入过了,可以直接使用 emit ,忘记打出来了,我放完整源码吧
    ```
    from hashlib import new
    from re import T
    from time import sleep

    import requests
    from flask import Flask, sessions, request
    import pymysql
    import json
    from flask_socketio import SocketIO, emit, join_room, leave_room, send

    db = pymysql.connect(host="gz-cynosdbmysql-grp-pre3qflf.sql.tencentcdb.com", port=21297, user="autowhitelist",
    password="nl6a0j2pcaLDIXcb", database="autowhitelist")
    cursor = db.cursor()
    app = Flask(__name__)

    # 以下是轮询方案,能不用就尽量不用
    """@app.route("/checkNew")
    def checkNew():
    secret = request.values.get("secret")
    newMission = cursor.execute("SELECT * FROM missionList WHERE secret=%s AND isEnd = 0", secret)
    print(newMission)
    if newMission == 0:
    return json.dumps({"status": "0", "msg": "No new mission"})
    else:
    mission = cursor.fetchone()
    id = mission[1]
    cursor.execute("UPDATE missionList SET isEnd = 1 WHERE id = %s", id)
    db.commit()
    return json.dumps({"status": "1", "msg": "New Whitelist", "id": id})"""

    """@app.route("/uploadNew")
    def uploadNew():"""

    """@app.route("/checkServer")
    def checkServer():
    secret = request.values.get("secret")
    isReg = cursor.execute("SELECT * FROM registerList WHERE secret=%s", secret)
    if isReg == 1:
    return ("Success")
    else:
    return ("Server not exist")
    """

    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    socketio = SocketIO(app)
    tokenList = {}


    @app.route("/test")
    def testConnect():
    sid = tokenList["114514"]
    socketio.emit('newMission', {'from': 'server'}, namespace="newMission", to=sid)
    return "success"


    @socketio.on('register')
    def register(data):
    print("服务器%s 尝试注册" % data["secret"])
    emit('register', {"token": "1919810"})
    print("注册成功,token 为%s" % "1919810")
    sid = request.sid
    join_room("mainRoom")
    tokenList["114514"] = sid


    """@socketio.on('message')
    def message(data):
    print(data) # {'from': 'client'}
    emit('response', {'from': 'server'})
    sleep(5)
    emit('response', "exit")"""

    if __name__ == '__main__':
    socketio.run(app, debug=True, host='127.0.0.1', port=8090)
    ```
    zhishixiang
        7
    zhishixiang  
    OP
       2022-07-13 17:50:45 +08:00
    @zhishixiang 又忘记删机密信息了,改个密码先
    NessajCN
        8
    NessajCN  
       2022-07-13 18:05:38 +08:00
    @zhishixiang 我大概知道啥问题了。你客户端里是 sio.connect("ws://localhost:8090"),
    但 socketio 虽然是用 websocket 实现的,你在连接的时候却不能这么写
    你改成 sio.connect("http://localhost:8090") 试试
    zhishixiang
        9
    zhishixiang  
    OP
       2022-07-13 18:52:14 +08:00
    @NessajCN 还是不行,而且会弹出报错(虽然没什么影响)
    message handler error
    Traceback (most recent call last):
    File "C:\Users\86177\AppData\Local\Programs\Python\Python310\lib\site-packages\engineio\server.py", line 622, in _trigger_event
    return self.handlers[event](*args)
    File "C:\Users\86177\AppData\Local\Programs\Python\Python310\lib\site-packages\socketio\server.py", line 730, in _handle_eio_message
    pkt = packet.Packet(encoded_packet=data)
    File "C:\Users\86177\AppData\Local\Programs\Python\Python310\lib\site-packages\socketio\packet.py", line 41, in __init__
    self.attachment_count = self.decode(encoded_packet)
    File "C:\Users\86177\AppData\Local\Programs\Python\Python310\lib\site-packages\socketio\packet.py", line 111, in decode
    self.data = self.json.loads(ep)
    File "C:\Users\86177\AppData\Local\Programs\Python\Python310\lib\json\__init__.py", line 346, in loads
    return _default_decoder.decode(s)
    File "C:\Users\86177\AppData\Local\Programs\Python\Python310\lib\json\decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
    File "C:\Users\86177\AppData\Local\Programs\Python\Python310\lib\json\decoder.py", line 355, in raw_decode
    raise JSONDecodeError("Expecting value", s, err.value) from None
    json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
    NessajCN
        10
    NessajCN  
       2022-07-13 19:10:05 +08:00
    @zhishixiang 这个报错就说明消息成功传达,只是参数读取出问题吧?譬如你传过去的{'secret':'114514'}这个参数直接 data["secret"]不行,那就 json.loads(data)再 data["secret"]
    zhishixiang
        11
    zhishixiang  
    OP
       2022-07-13 19:18:32 +08:00
    @NessajCN 已经读取成功了,这个报错是服务端的,完全不影响使用,而且是不知道改了什么才出现的,根本修复不了,很奇怪
    NessajCN
        12
    NessajCN  
       2022-07-13 19:22:33 +08:00
    @zhishixiang 那不对啊,你贴的报错信息是 socketio 这个包(也就是 python-socketio ,你客户端用的就是这个)的。但你服务器端代码用的是 flask_socketio 。报错怎么会报没在用的包的错?
    zhishixiang
        13
    zhishixiang  
    OP
       2022-07-13 21:30:34 +08:00 via Android
    @NessajCN 会不会是 flask socketio 封装了 socketio 的相关内容
    raycool
        14
    raycool  
       2022-07-13 23:05:41 +08:00
    直接用 tornado 或者 fastapi
    zoofy
        15
    zoofy  
       2022-07-14 16:27:31 +08:00   ❤️ 1
    给你贴个可以运行的代码
    ``` 服务端
    from flask import Flask
    from flask_socketio import SocketIO, emit, join_room
    from loguru import logger

    app = Flask(__name__)
    app.config['SECRET_KEY'] = 'secret!'
    socketio = SocketIO(app)
    tokenList = {}


    @app.route("/test")
    def testConnect():
    sid = tokenList["114514"]
    data = {'from': 'server'}
    # socketio.emit('newMission', data, namespace='/mission')
    socketio.emit('newMission', data, to=sid, namespace='/mission')
    return 'success'


    @socketio.on('register')
    def register(data):
    sid = data['sid']
    print("服务器%s 尝试注册" % data["secret"])
    emit('register', {"token": sid})
    logger.debug("注册成功,token 为%s" % sid)
    join_room(sid)
    tokenList["114514"] = sid


    @socketio.on('message', namespace='/mission')
    def message(data):
    print(data) # {'from': 'client'}
    # emit('response', {'from': 'server'})
    # sleep(5)
    # emit('response', "exit")


    if __name__ == '__main__':
    socketio.run(app, debug=True, host='127.0.0.1', port=8090)
    ```

    ``` 客户端
    import socketio

    sio = socketio.Client()


    @sio.on('connect')
    def on_connect():
    print('正在注册,请稍后')
    # 获取 namespace sid, 发送给 server
    sid = sio.get_sid('/mission')
    sio.emit('register', {'secret': '114514', 'sid': sid})


    @sio.on('register')
    def isReg(data):
    print("receiver message from register ,当前 token 为", data['token'])
    pass


    @sio.on("newMission", namespace='/mission')
    def newMission(data):
    print("get mission message data: ", data)


    sio.connect('ws://localhost:8090')
    sio.wait()
    ```

    主要是获取到 namespace 后,把 namespace sid join room, 记录好 sid. 要 emit 的时候, 加上 namespace 和 roomId(sid)进行发送
    zhishixiang
        16
    zhishixiang  
    OP
       2022-07-14 17:21:57 +08:00
    @zoofy 还是不行,而且有几个问题:
    1.sid 应该用 request.sid 获取,data 数据里面没有 sid 内容
    2.目的是为了当访问 localhost:8090/test 时客户端能收到消息,但是仍未实现
    3.要是实在无法解决的话有没有别的方法能实现 websocket 连接
    zoofy
        17
    zoofy  
       2022-07-14 18:37:09 +08:00
    @zhishixiang 你的 namespace 跟 sid 要和客户端相同的,所以在连接的时候获取到的 sid 传送给服务端,记录好,test 接口才能发信息给客户端。不能在服务端获取啊
    zhishixiang
        18
    zhishixiang  
    OP
       2022-07-14 18:39:53 +08:00
    @zoofy 意思就是说 sid 必须要客户端发给服务端,服务端无法获取吗,那服务端怎么通过客户端提供的 sid 找到客户端并向客户端发送消息
    zoofy
        19
    zoofy  
       2022-07-14 18:50:24 +08:00
    @zhishixiang 不一样的,你可以打印看看。我那个代码可以直接允许的了
    zhishixiang
        20
    zhishixiang  
    OP
       2022-07-14 19:12:07 +08:00
    @zoofy 服务端第 13 行 sid 参数是从客户端获取的,但是客户端没有传 sid 参数,实际上无法运行
    zhishixiang
        21
    zhishixiang  
    OP
       2022-07-14 19:12:36 +08:00
    @zoofy 我看错了不好意思
    zhishixiang
        22
    zhishixiang  
    OP
       2022-07-14 19:14:21 +08:00
    @zoofy 确实可以用了,我测试时运行了我自己的代码导致出错,感谢大佬帮助
    zhishixiang
        23
    zhishixiang  
    OP
       2022-07-14 21:21:31 +08:00
    @zoofy 还有个问题:怎么检测客户端断开链接并移除房价以及打印日志
    zoofy
        24
    zoofy  
       2022-07-15 11:48:07 +08:00
    @zhishixiang 感觉可以在注册的时候记录好 request.sid 和 request namespace sid 两个 id, 在服务端监听 disconnect 方法,获取到 request.sid 后拿到对应保存的 namespace sid, 然后再做 close room 或者 leave room 操作
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1021 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 20:43 · PVG 04:43 · LAX 12:43 · JFK 15:43
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.