WebSocket
目录
一、WebSocket -网络通信协议
1-1 简介
二、Websockets servers and clients in Python
2-0 connect
2-0-1 建立一对一短连接
2-0-2 建立一对一长连接
2-0-3 建立一对多长连接
2-1 asyncio
三、SocketIO
3-0 Flask-Sockets VS Flask-SocketIO
3-1 Socket.IO
3-2 python-socketio
3-2-0 安装
3-2-1 服务端基本总结
3-2-3 一对多长连接
3-2 Flask-SocketIO
四、WebSocket for client - 单纯用于连接的模块
五、Tornado - 一个支持HTTP和WebSocket的Web框架
六、Aiohttp - 基于asyncio,一个支持HTTP和WebSocket的框架
一、WebSocket -网络通信协议
Web 基于 B/S 架构,通常使用 HTTP 协议进行通信,HTTP 本质是一个单向请求,若需要持续的获取服务端的数据变化,必须轮询(polling)进行数据请求【每隔一段时间发送request,服务器将新数据返回】。
使用HTTP协议处理服务端数据监控的弊端:轮询效率低,浪费资源。因为必须不停建立连接,或保持HTTP始终连接。
1-1 简介
为了解决上述的需求问题,并提高数据传输效率,WebSocket 协议就出现了。
WebSocket 协议下,服务端和客户端能相互的主动发送消息,建立平等对话。属于服务器推送技术的一种。
WebSocket 一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 是独立的、创建在 TCP 上的协议,和 HTTP 的唯一关联是使用 HTTP 协议的101状态码进行协议切换,使用的 TCP 端口是80,可以用于绕过大多数防火墙的限制。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端直接向客户端推送数据而不需要客户端进行请求,在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并允许数据进行双向传送。
二、Websockets servers and clients in Python
它构建于asyncio(协程)Python的标准异步I / O框架之上,提供了一个优雅的基于协程的API。
即,使用 asyncio 包裹的基本python实现方式
2-0 connect
2-0-1 建立一对一短连接
# ----------- server 端 -----------
import asyncio
import websockets
async def hello(websocket, path):
print('-------- hello opened ------')
name = await websocket.recv()
print(name)
greeting = 'hello %s' % name
await websocket.send(greeting)
print(greeting)
start_server = websockets.serve(hello, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
# ----------- client 端 -----------
import asyncio
import websockets
async def hello():
async with websockets.connect(
'ws://localhost:8765') as websocket:
name = input('your name:')
await websocket.send(name)
print(name)
greeting = await websocket.recv()
print(greeting)
asyncio.get_event_loop().run_until_complete(hello())
2-0-2 建立一对一长连接
# ----------- server 端 -----------
import asyncio
import websockets
async def producer_handler(websocket, path):
print('---- 建立了连接 -----')
while True:
message = input('please input:')
await websocket.send(message)
start_server = websockets.serve(producer_handler, 'localhost', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
# ----------- client 端 -----------
import asyncio
import websockets
async def consumer_handler():
async with websockets.connect(
'ws://localhost:8765') as websocket:
async for message in websocket:
print(message)
asyncio.get_event_loop().run_until_complete(consumer_handler())
2-0-3 建立一对多长连接
# ----------- server 端 -----------
import asyncio
import logging
import websockets
logging.basicConfig()
USERS = set()
async def notify_users():
# 对注册列表内的客户端进行推送
if USERS: # asyncio.wait doesn't accept an empty list
message = input('please input:')
await asyncio.wait([user.send(message) for user in USERS])
async def register(websocket):
USERS.add(websocket)
await notify_users()
async def unregister(websocket):
USERS.remove(websocket)
await notify_users()
async def counter(websocket, path):
# register(websocket) sends user_event() to websocket
await register(websocket)
try:
# 处理客户端数据请求 (业务逻辑)
async for message in websocket:
print(message)
finally:
await unregister(websocket)
asyncio.get_event_loop().run_until_complete(
websockets.serve(counter, 'localhost', 6789))
asyncio.get_event_loop().run_forever()
# ----------- client 端 -----------
async def consumer_handler():
async with websockets.connect(
'ws://localhost:6789') as websocket:
async for message in websocket:
print(message)
asyncio.get_event_loop().run_until_complete(consumer_handler())
2-1 asyncio
三、SocketIO
3-0 Flask-Sockets VS Flask-SocketIO
Flask-Sockets
Flask-Sockets 只是实现通信通道,发送的是完全取决于应用程序。
Flask-Sockets 仅仅将WebSocket协议(通过使用gevent-websocket项目)进行包装,因此它 只适用于原生支持WebSocket协议的浏览器,对于那些不支持WebSocket协议的较老的浏览器,无法使用
Flask-SocketIO
Flask-SocketIO不仅实现了WebSocket协议,并且对于那些不支持WebSocket协议的旧版浏览器,使用它也能够实现相同的效果。新版旧版的浏览器都能使用他 。
Flask-SocketIO 实现了SocketIO Javascript库公开的消息传递协议。
Flask-SocketIO 还为事件处理程序创建了一个类似flask的常规视图函数的环境,包括创建应用程序和请求上下文。 然而,在文档中会介绍一些重要的例外情形。
3-1 Socket.IO
3-2 python-socketio
3-2-0 安装
服务端安装:pip install python-socketio
客户端安装:pip install "python-socketio[client]"
安装问题 :module 'importlib._bootstrap' has no attribute 'SourceFileLoader'
错误分析:setuptools版本过久,需要更新
解决方式:python -m ensurepip --upgrade
3-2-1 服务端基本总结
import eventlet
import socketio
# create a Socket.IO server
sio = socketio.Server()
# wrap with a WSGI application
app = socketio.WSGIApp(sio)
# 事件处理函数的两种写法
# sid:每个客户端连接的唯一标识符,一个客户端发送的所有事件具有相同的sid值
@sio.event
def my_event(sid, data):
pass
@sio.on('my custom event')
def another_event(sid, data):
pass
# connetc函数 在客户端连接时自动调用 可用于验证用户身份等
# environ 为字典格式,包含请求信息、http头
@sio.event
def connect(sid, environ):
print('connect ', sid)
# 若返回 False 则表示拒绝与客户端的联系
# return False
# 抛错,将所有参数通过拒绝消息发送给客户端
raise ConnectionRefusedError('authentication failed')
# disconnect函数 在客户端断开连接时自动调用
@sio.event
def disconnect(sid):
print('disconnect ', sid)
# socketio.Server.emit() 发送事件
# sio.emit('事件名',{'具体信息数据': '……'})
sio.emit('my event', {'data': 'foobar'})
# room 用于标识应接受该事件的客户端sid,需要设置客户端的sid,若省略则表示广播事件
sio.emit('my event', {'data': 'foobar'}, room='user_sid')
# callback 回调函数,将在客户端处理事件后调用该函数,客户端返回的任何值都将作为参数给予该回调函数。
# 若在广播的情况下使用回调,则服务端将有大量的调用次数
sio.emit('my event', {'data': 'foobar'}, callback=my_event)
# namespace 命名空间
# client 为每个连接制定不同的命名空间,来打开多个连接,命名空间将作为主机名和端口后的路径名
# http://example.com:8000/chat
@sio.event(namespace='/chat')
def my_custom_event(sid, data):
pass
@sio.on('my custom event', namespace='/chat')
def my_custom_event(sid, data):
pass
# socketio.Namespace 基于类的命名空间
# 注意:基于类的命名空间为单例,所以命名空间实例不能用于存储客户端的特定消息
class MyCustomNamespace(socketio.Namespace):
# 服务器接受的任何事件,都将调用 on_ 前缀的事件名方法
# 若接受到的事件名在类内无匹配on前缀方法,则忽略。
def on_connect(self, sid, environ):
pass
def on_disconnect(self, sid):
pass
# my_event 事件触发 on_my_event 方法的执行
def on_my_event(self, sid, data):
self.emit('my_response', data)
sio.register_namespace(MyCustomNamespace('/test'))
# room 指定用户组
# socketio.Server.enter_room() 和 socketio.Server.leave_room()方法管理其中的客户端
@sio.event
def begin_chat(sid):
sio.enter_room(sid, 'chat_users')
@sio.event
def exit_chat(sid):
sio.leave_room(sid, 'chat_users')
# skip_sid 用于跳过该sid的客户端,不进行消息推送
@sio.event
def my_message(sid, data):
sio.emit('my reply', data, room='chat_users', skip_sid=sid)
# session 用户信息存储和检索
# 注意:客户端断开连接时,会破坏用户会话的内容。
# 特别是,当客户端在意外断开与服务器的连接后重新连接时,不会保留用户会话内容。
@sio.event
def connect(sid, environ):
username = environ
# username = authenticate_user(environ)
sio.save_session(sid, {'username': username})
@sio.event
def message(sid, data):
session = sio.get_session(sid)
print('message from ', session['username'])
# 基于上下文,管理session
@sio.event
def connect(sid, environ):
username = environ
# username = authenticate_user(environ)
with sio.session(sid) as session:
session['username'] = username
@sio.event
def message(sid, data):
with sio.session(sid) as session:
print('message from ', session['username'])
3-2-3 一对多长连接
3-2 Flask-SocketIO
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
@socketio.on('connect')
def connect():
print('connection established')
@socketio.on('my_event')
def handle_json(json):
print('received json: ' + str(json))
@socketio.on('disconnect')
def test_disconnect():
print('Client disconnected')
if __name__ == '__main__':
socketio.run(app)
四、WebSocket for client - 单纯用于连接的模块
安装:Type "python setup.py install" or "pip install websocket-client" to install.
五、Tornado - 一个支持HTTP和WebSocket的Web框架
六、Aiohttp - 基于asyncio,一个支持HTTP和WebSocket的框架