什么是websocket协议?

Posted by DDW on 07-30,2022

一、什么是websocket

    当没有客户端传送消息给服务端的时候,服务器是不会有任何响应的,只有客户端发送请求后,服务端才会做出响应,这种是单向的,也就是http协议,这种就叫做半双工。
    为什么会有这样的设计?因为当时网页设计出来只是为了适用于查看网页的场景,但现在浏览器已发展为能够进行网游、视频观看等多种大量相互数据交互的场景,那么半双工已经无法满足此类场景,因此,能够进行大量数据交互的websocket,也称作为全双工模式应运而生。
http协议:
image-1690709337135
websocket:
image-1690709457702

二、websocket如何实现?

    浏览器为兼容所有的模式,在开始的时候依旧使用HTTP,如果需要升级到websocket链接,就会在Http请求里面携带一个特殊的header,其中,connection:Upgrade表面浏览器想升级协议。Upgrade:websocket表示浏览器想升级为websocket协议,同时附带一个Sec-webSocket-KeyBASE64码发给服务器,如果服务器支持升级成websocket协议,走回走websocket协议的握手流程,根据客户端生成的BASE64码,用公开算法变成另一个字符串,放在响应的Sec-ws-accept里,附带101状态码代表协议切换,然后浏览器比较回传的字符串,如果是一样的,那验证通过。image-1690710036542
websocket其实也和http有点类似,在发送的数据前会有4位opcode代表不同的数据类型,比如=1是text类型,=2是二进制数据类型,=8是关闭连接的信号。后续跟的payload字段表示的是我们真正传输数据的长度,单位是字节,比如数据包是字符串111,那么payload长度就是3,后面还有扩展用的16+32+16bit,总共7+64bit,可以代表很长的数据传输。ws会用最开始的7位当做标志位,不管数据有多大,都是先读前面的7个Bit,根据他的取值,如果是0~125,那么就是全部长度。如果大于125,会继续读下面的64个Bit。接着剩下的Payload数据才是真正的数据,在知道长度之后,根据这个值截取对应的数据,不会产生粘包的情况。image-1690710598911

三、代码实现

image-1690717519133
这就是ws的URL,ws和wss对应的是http和https
websocket握手消息示例:

GET /chat HTTP/1.1
Host: normal-website.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w==
Connection: keep-alive, Upgrade
Cookie: session=KOsEJNuflw4Rd9BDNrVmvwBF9rEijeE2
Upgrade: websocket

如果 Server 接收连接,返回响应

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: 0FFP+2nmNIf/h+4BP36k9uzrYGk=s

三、使用python websocket库搭建服务

websocket可以支持多个用户共同访问,采用异步模式编程
这是服务端代码,websocket自带心跳机制,所以不用担心,客户端有任何风吹草动,都会断开连接。

import websockets  # 导入websockets库
import asyncio  # 导入asyncio异步的库,可以使方法并发异步进行
from datetime import datetime


async def handler(websocket):
    data = await websocket.recv()  # 这一行使用 await 挂起协程,直到从WebSocket接收到数据。收到的数据存放在 data 变量中。
    reply = f"Data received as \"{data}\".time:{datetime.now()}"  # 这一行创建了一个格式化字符串,包含了接收到的数据和当前的时间。
    print(reply)  # print(reply):打印回复的内容
    await websocket.send(reply)  # 这一行使用 await 挂起协程,直到回复的数据发送到WebSocket。
    print('Send reply')


async def main():
    async with websockets.serve(handler, "localhost",
                                9999):  # 使用 websockets.serve 启动一个WebSocket服务器,监听 localhost 的 9999 端口。所有新的连接都会使用 handler 函数来处理
        await asyncio.Future()  # run forever


if __name__ == "__main__":
    asyncio.run(main())

这是客户端代码,同样采用异步的方式:

import websockets
import time
import asyncio


async def ws_client(url):  # 这行代码定义了一个异步函数 ws_client,它接受一个参数 url,这个 url 是 WebSocket 服务器的地址。
    for i in range(1, 40):
        async with websockets.connect(
                url) as websocket:  # 这行代码使用 async with 语句和 websockets.connect() 函数建立一个 WebSocket 连接。连接成功后,返回的 WebSocket 对象被赋值给 websocket
            await websocket.send(
                "Hello, I am PyPy.")
            response = await websocket.recv()
        print(response)
        time.sleep(1)


asyncio.run(
    ws_client('ws://localhost:9999'))  # 这行代码使用 asyncio.run() 函数启动 ws_client 协程,并将 'ws://localhost:9999' 作为参数传递给它。