Basics of Python and Django. / Библиотека socketio. / Используем веб-сокеты с библиотекой socketio.
Используем веб-сокеты с библиотекой socketio.
Промышленные инструменты для вебсокетов https://www.emqx.io/ centrifugo.
Создадим образ для веб-сервера Dockerfile-nginx.
FROM nginx:latest
RUN mkdir /app
COPY ./default.conf /etc/nginx/conf.d/default.conf
Конфигурация виртуального хоста default.conf.
server {
listen 80;
server_name localhost;
location / {
root /app;
try_files $uri /index.html;
}
}
Создадим образ для сокет-сервера Dockerfile-socketio.
FROM python:3.6
ENV PYTHONUNBUFFERED 1
RUN mkdir /app
WORKDIR /app
COPY ./requirements.txt /app/requirements.txt
RUN apt update
RUN pip install -r requirements.txt
CMD python server.py
Создадим сборщик контейнеров docker-compose.yaml.
version: '3.5'
services:
socketio:
build:
context: .
dockerfile: Dockerfile-socketio
volumes:
- .:/app
container_name: socket-io-server
ports:
- 8050:5000
socketio-nginx:
build:
context: .
dockerfile: Dockerfile-nginx
volumes:
- .:/app
ports:
- 8088:80
container_name: socket-io-nginx
Установим клиентские зависимости.
npm init
npm install jquery socket.io @types/socket.io --save
Создадим index.html
<!DOCTYPE html>
<html>
<head><title>Socket io</title>
<script src="/node_modules/socket.io/client-dist/socket.io.min.js"></script>
<script src="/node_modules/jquery/dist/jquery.min.js"></script>
</head>
<body>
<h1>Test socketio</h1>
<button id="button">Test</button>
<script>
const socket = io('ws://localhost:8050', {transports:['websocket']});
socket.on('connect', () => {
socket.on('message', msg => {
console.log(msg);
})
});
$('#button').on('click',() => {
socket.emit('test_message',{data: 'ping'});
});
</script>
</body>
</html>
Создадим простой сокет-сервер server.py.
import eventlet
import socketio
sio = socketio.Server(cors_allowed_origins='*',async_mode='eventlet')
app = socketio.WSGIApp(sio)
@sio.event
def connect(sid, environ):
print('connect ', sid)
@sio.event
def test_message(sid, data):
print('message ', data)
sio.emit('message', {'data': 'pong'}, broadcast=True, include_self=True)
# sio.emit('my event', {'data': 'foobar'}, room=user_sid)
@sio.event
def disconnect(sid):
print('disconnect ', sid)
if __name__ == '__main__':
eventlet.wsgi.server(eventlet.listen(('', 5000)), app)
Возможности библиотеки
Пространства имен.
При коннекте можно указывать неймспейс.
ws://example.com:8000/chat
И потом с ним работать.
@sio.event(namespace='/chat')
def my_custom_event(sid, data):
pass
Отправка сообщения определенному клиенту
sio.emit('my event', {'data': 'foobar'}, room=user_sid)
Добавление соединений в комнаты
@sio.event
def begin_chat(sid):
sio.enter_room(sid, 'chat_users')
@sio.event
def exit_chat(sid):
sio.leave_room(sid, 'chat_users')
После чего становится возможным отправка сообщения в комнату.
sio.emit('my reply', data, room='chat_users', skip_sid=sid)
Одно соединение может быть добавлено в множество комнат.
Сессия пользователя.
Есть возможность сохранять специфичные данные пользователя для каждого соединения.
@sio.event
def connect(sid, environ):
sio.save_session(sid, {'username': 'Dima'})
@sio.event
def message(sid, data):
session = sio.get_session(sid)
Брать данные сессии можно и так:
with sio.session(sid) as session:
session['username'] = username
Взаимодействие со сторонними процессами.
Часто возникает необходимость передать данные сокет-серверу из других приложений или процессов.
Для этого можно использовать посредника в виде БД Redis.
pip install redis
Создадим новый контейнер.
version: '3.5'
services:
socketio-server:
...
depends_on:
- "socketio-redis"
...
socketio-redis:
image: "redis:alpine"
ports:
- "6999:6379"
Зададим менеджер сообщений при создании объекта приложения сервера.
import eventlet
eventlet.monkey_patch()
mgr = socketio.RedisManager('redis://socketio-redis:6379/0')
sio = socketio.Server(cors_allowed_origins='*',async_mode='eventlet',client_manager=mgr)
socketio-redis - в докере можно пользоваться именами контейнеров для обозначений их хостов
Создадим переодическую рассылку серверного времени в отдельном файле timer.py.
import socketio
import time
from datetime import datetime
mgr = socketio.RedisManager('redis://localhost:6999/0', write_only=True)
while True:
time.sleep(2)
now = datetime.now()
current_time = now.strftime("%H:%M:%S")
mgr.emit('message', data={'time': current_time})
print(current_time)
Сделаем обновление на клиенте.
<div id="time"></div>
<script>
const socket = io('ws://localhost:8050', {transports:['websocket']});
socket.on('connect', () => {
socket.on('message', msg => {
$('#time').html(msg.time);
})
});
...
Добавим новый контейнер.
....
socketio-server:
...
command: ["python", "server.py"]
socketio-pinger:
build:
context: .
dockerfile: Dockerfile-socketio
volumes:
- .:/app
container_name: socket-io-pinger
depends_on:
- "socketio-redis"
command: ["python", "pinger.py"]
При этом удалив команду запуска приложения из Dockerfile-socketio и перенесем в docker-compose.