美文网首页fastapi
FastAPI 二(简单的多人聊天室WebSocket)

FastAPI 二(简单的多人聊天室WebSocket)

作者: 地雷 | 来源:发表于2020-01-17 11:26 被阅读0次

    简单的多人聊天室


    1.复制官网代码并启动一个简单的服务

    https://fastapi.tiangolo.com/advanced/websockets/

    复制

    现在不需要关注太多,只是熟悉一下FastApi
    我现在复制并创建了一个名为main.py的文件,内容如下

    from fastapi import FastAPI
    from starlette.responses import HTMLResponse
    from starlette.websockets import WebSocket
    
    app = FastAPI()
    
    html = """
    <!DOCTYPE html>
    <html>
        <head>
            <title>Chat</title>
        </head>
        <body>
            <h1>WebSocket Chat</h1>
            <form action="" onsubmit="sendMessage(event)">
                <input type="text" id="messageText" autocomplete="off"/>
                <button>Send</button>
            </form>
            <ul id='messages'>
            </ul>
            <script>
                var ws = new WebSocket("ws://localhost:8000/ws");
                ws.onmessage = function(event) {
                    var messages = document.getElementById('messages')
                    var message = document.createElement('li')
                    var content = document.createTextNode(event.data)
                    message.appendChild(content)
                    messages.appendChild(message)
                };
                function sendMessage(event) {
                    var input = document.getElementById("messageText")
                    ws.send(input.value)
                    input.value = ''
                    event.preventDefault()
                }
            </script>
        </body>
    </html>
    """
    # @app.get("/")告诉FastAPI如何去处理请求
    # 路径 /
    # 使用get操作
    @app.get("/")
    async def get():
       # 返回表单信息
        return HTMLResponse(html)
    
    @app.websocket("/ws")
    async def websocket_endpoint(websocket: WebSocket):
        await websocket.accept()
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Message text was: {data}")
    

    启动

    打开cmd输入以下命令
    uvicorn main:app --reload
    Windows启动成功显示如下

    (FastApi) C:\Users\admin\Desktop\FastAPI>uvicorn main:app --reload
    �[32mINFO�[0m:     Uvicorn running on �[1mhttp://127.0.0.1:8000�[0m (Press CTRL+C to quit)
    �[32mINFO�[0m:     Started reloader process [�[36m�[1m8532�[0m]
    �[32mINFO�[0m:     Started server process [�[36m1952�[0m]
    �[32mINFO�[0m:     Waiting for application startup.
    �[32mINFO�[0m:     Application startup complete.
    

    群发

    打开以下链接
    http://localhost:8000/
    如果一切顺利的话,可以看到以下内容。
    这里是我输入了一些内容

    1579225770(1).png

    到这里 可能你已经发现了,现在并不是一个聊天室,就算你打开了多个页面也不能互相交流,只能看到自己发送的内容

    2.思考如何修改

    参考
    https://www.starlette.io/websockets/
    我们把代码修改为类(Class)的形式,内容如下文件名为main2.py
    请复制以下代码

    from fastapi import FastAPI
    # 基于类的视图模式来处理HTTP方法和WebSocket会话。
    from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
    from starlette.responses import HTMLResponse
    # 路由
    from starlette.routing import Route, WebSocketRoute
    
    html = """
    <!DOCTYPE html>
    <html>
        <head>
            <title>Chat</title>
        </head>
        <body>
            <h1>WebSocket Chat</h1>
            <form action="" onsubmit="sendMessage(event)">
                <input type="text" id="messageText" autocomplete="off"/>
                <button>Send</button>
            </form>
            <ul id='messages'>
            </ul>
            <script>
                var ws = new WebSocket("ws://localhost:8000/ws");
                ws.onmessage = function(event) {
                    var messages = document.getElementById('messages')
                    var message = document.createElement('li')
                    var content = document.createTextNode(event.data)
                    message.appendChild(content)
                    messages.appendChild(message)
                };
                function sendMessage(event) {
                    var input = document.getElementById("messageText")
                    ws.send(input.value)
                    input.value = ''
                    event.preventDefault()
                }
            </script>
        </body>
    </html>
    """
    
    class Homepage(HTTPEndpoint):
        async def get(self, request):
            return HTMLResponse(html)
    
    
    class Echo(WebSocketEndpoint):
        encoding = "text"
        
        # 连接
        async def on_connect(self, websocket):
            await websocket.accept()
            
        # 收发
        async def on_receive(self, websocket, data):
            await websocket.send_text(f"Message text was: {data}")
            
        # 断开
        async def on_disconnect(self, websocket, close_code):
            pass
    
    routes = [
        Route("/", Homepage),
        WebSocketRoute("/ws", Echo)
    ]
    
    app = FastAPI(routes=routes)
    

    打开cmd输入以下命令
    uvicorn main2:app --reload
    打开以下链接
    http://localhost:8000/
    输入一些内容,发现和函数的写法并没有什么区别,依然是单人聊天。
    接下来让我们思考一下

    问题1.如何转发多人消息

    我想让这个on_receive函数在发送内容的时候发送给连接上的所有人

    思考

    # 收发
    async def on_receive(self, websocket, data):
            await websocket.send_text(f"Message text was: {data}")
    

    可以看到每次发送都要使用websocket.send_text方法
    每个加入连接的用户都是一个websocket对象
    我能不能把这个对象保存下来每次发送的时候循环所有对象发送呢

    行动

    本着最简单的方法,我做了以下修改

    • 创建了一个database.py文件来存websocket对象
    # 没错 这个文件就只有一个名为info的列表
    info = []
    
    • 在用户连接的时候保存websocket对象
    # 先引用刚刚创建的database.py文件里的info
    from database import info
    """ ...省略内容... """
    # 连接
        async def on_connect(self, websocket):
            await websocket.accept()
            # 保存websocket对象
            info.append(websocket)
            # 打印一下
            print(info)
            
    
    • 在发送信息的时候给所有websocket对象发送
    # 收发
        async def on_receive(self, websocket, data):
            for wbs in info:
                await wbs.send_text(f"Message text was: {data}")
    
    • 连接断开的时候删除websocket对象
        # 断开
        async def on_disconnect(self, websocket, close_code):
            # 删除websocket对象
            info.remove(websocket)
            # 再打印看看
            print(info)
            pass
    
    • 修改后的内容如下
    from fastapi import FastAPI
    # 基于类的视图模式来处理HTTP方法和WebSocket会话。
    from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
    from starlette.responses import HTMLResponse
    # 路由
    from starlette.routing import Route, WebSocketRoute
    from database import info
    
    html = """
    <!DOCTYPE html>
    <html>
        <head>
            <title>Chat</title>
        </head>
        <body>
            <h1>WebSocket Chat</h1>
            <form action="" onsubmit="sendMessage(event)">
                <input type="text" id="messageText" autocomplete="off"/>
                <button>Send</button>
            </form>
            <ul id='messages'>
            </ul>
            <script>
                var ws = new WebSocket("ws://localhost:8000/ws");
                ws.onmessage = function(event) {
                    var messages = document.getElementById('messages')
                    var message = document.createElement('li')
                    var content = document.createTextNode(event.data)
                    message.appendChild(content)
                    messages.appendChild(message)
                };
                function sendMessage(event) {
                    var input = document.getElementById("messageText")
                    ws.send(input.value)
                    input.value = ''
                    event.preventDefault()
                }
            </script>
        </body>
    </html>
    """
    
    class Homepage(HTTPEndpoint):
        async def get(self, request):
            return HTMLResponse(html)
    
    class Echo(WebSocketEndpoint):
        encoding = "text"
        
        # 连接
        async def on_connect(self, websocket):
            await websocket.accept()
             # 保存websocket对象
            info.append(websocket)
            # 打印一下
            print(info)
            
        # 收发
        async def on_receive(self, websocket, data):
            for wbs in info:
                await wbs.send_text(f"Message text was: {data}")
            
        # 断开
        async def on_disconnect(self, websocket, close_code):
            # 删除websocket对象
            info.remove(websocket)
            # 再打印看看
            print(info)
            pass
    
    routes = [
        Route("/", Homepage),
        WebSocketRoute("/ws", Echo)
    ]
    
    app = FastAPI(routes=routes)
    

    启动

    这里我打开了两个页面,可以看到两个页面都收到了消息,也看到了两个websocket对象

    1579230009.jpg

    3.添加用户标识

    虽然已经达到了群发的作用,但是根本分不清到底是谁在发送消息
    这里我就不写思考流程了,靠猜
    这里也修改了页面内容

    from fastapi import FastAPI
    from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
    from starlette.responses import HTMLResponse
    from starlette.routing import Route, WebSocketRoute
    from database import info
    
    html = """
    <!DOCTYPE html>
    <html>
        <head>
            <title>Chat</title>
        </head>
        <body>
            <h1>WebSocket Chat</h1>
            <form action="" onsubmit="sendMessage(event)">
                <input type="text" id="messageText" autocomplete="off" placeholder="" />
                <button>Send</button>
            </form>
            <ul id='messages'>
            </ul>
            <script>
                document.getElementById("messageText").placeholder="第一次输入内容为昵称";
            
                var ws = new WebSocket("ws://localhost:8000/ws");
                
                // 接收
                ws.onmessage = function(event) {
                    // 获取id为messages的ul标签内
                    var messages = document.getElementById('messages')
                    // 创建li标签
                    var message = document.createElement('li')
                    // 创建内容
                    var content = document.createTextNode(event.data)
                    // 内容添加到li标签内
                    message.appendChild(content)
                    // li标签添加到ul标签内
                    messages.appendChild(message)
                };
                
                var name = 0;
                // 发送
                function sendMessage(event) {
                    var input = document.getElementById("messageText")
                    ws.send(input.value)
                    input.value = ''
                    event.preventDefault()
                    
                    if (name == 0){
                        document.getElementById("messageText").placeholder="";
                        name = 1;
                    }
                }
            </script>
        </body>
    </html>
    """
    
    class Homepage(HTTPEndpoint):
        async def get(self, request):
            return HTMLResponse(html)
    
    
    class Echo(WebSocketEndpoint):
        encoding = "text"
        
        # 修改socket
        async def alter_socket(self, websocket):
            socket_str = str(websocket)[1:-1]
            socket_list = socket_str.split(' ')
            socket_only = socket_list[3]
            return socket_only
        
        # 连接 存储
        async def on_connect(self, websocket):
            await websocket.accept()
            
            # 用户输入名称
            name = await websocket.receive_text()
            
            socket_only = await self.alter_socket(websocket)
            # 添加连接池 保存用户名
            info[socket_only] = [f'{name}', websocket]
    
            # 先循环 告诉之前的用户有新用户加入了
            for wbs in info:
                await info[wbs][1].send_text(f"{info[socket_only][0]}-加入了聊天室")
            
            print(info)
            
        # 收发
        async def on_receive(self, websocket, data):
            socket_only = await self.alter_socket(websocket)
            
            for wbs in info:
                await info[wbs][1].send_text(f"{info[socket_only][0]}: {data}")
            
    
        # 断开 删除
        async def on_disconnect(self, websocket, close_code):
            socket_only = await self.alter_socket(websocket)
            # 删除连接池
            info.pop(socket_only)
            print(info)
            pass
    
    routes = [
        Route("/", Homepage),
        WebSocketRoute("/ws", Echo)
    ]
    
    app = FastAPI(routes=routes)
    

    启动

    可以看到现在发送的消息前已经加上了用户的标识,其实只是把info的列表改为了字典,稍微修改了以下页面。


    1579231220(1).jpg

    ORZ太懒了

    其实还有很多需要优化的地方,看到的话可以自己试试
    1,用户第一次输入的内容为昵称是否合适
    2,用户退出没有群发退出消息
    3,页面优化 ① 聊天内容太多无法显示

    相关文章

      网友评论

        本文标题:FastAPI 二(简单的多人聊天室WebSocket)

        本文链接:https://www.haomeiwen.com/subject/rxgnoctx.html