一、什么是websocket
当没有客户端传送消息给服务端的时候,服务器是不会有任何响应的,只有客户端发送请求后,服务端才会做出响应,这种是单向的,也就是http协议,这种就叫做半双工。
为什么会有这样的设计?因为当时网页设计出来只是为了适用于查看网页的场景,但现在浏览器已发展为能够进行网游、视频观看等多种大量相互数据交互的场景,那么半双工已经无法满足此类场景,因此,能够进行大量数据交互的websocket,也称作为全双工模式应运而生。
http协议:
websocket:
二、websocket如何实现?
浏览器为兼容所有的模式,在开始的时候依旧使用HTTP,如果需要升级到websocket链接,就会在Http请求里面携带一个特殊的header,其中,connection:Upgrade表面浏览器想升级协议。Upgrade:websocket表示浏览器想升级为websocket协议,同时附带一个Sec-webSocket-KeyBASE64码发给服务器,如果服务器支持升级成websocket协议,走回走websocket协议的握手流程,根据客户端生成的BASE64码,用公开算法变成另一个字符串,放在响应的Sec-ws-accept里,附带101状态码代表协议切换,然后浏览器比较回传的字符串,如果是一样的,那验证通过。
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数据才是真正的数据,在知道长度之后,根据这个值截取对应的数据,不会产生粘包的情况。
三、代码实现
这就是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' 作为参数传递给它。