предисловие
Я не публиковал сообщения 2 месяца и увидел, как кто-то спрашивает: «Где этот выделенный поисковый робот Куинн?» ', я быстро выскочил.
В качестве примечания: идентификатор Wings of Demacia — Quinn теперь AsyncIns.Я планирую поехать в Пекин этим летом, прежде чем я поеду, мне нужно подготовиться технически, поэтому я недавно учился. Мой способ обучения прост и понятен: читать документацию, читать исходный код и строить колеса. Создание колес, на мой взгляд, самый быстрый и эффективный способ добиться прогресса.
Некоторое время назад некоторые данные нужно было сканировать через WebSocket, введение в онлайн-статьи, использовалась библиотека websocket-client. Но мой проект асинхронный, я надеюсь, что данные чтения веб-сокетов также могут быть асинхронными, затем я искал на github веб-сокеты в библиотеке, использовал и читал исходный код, я нашел веб-сокеты, все еще не мой взгляд, идеальная библиотека, я решил разработать веб-сокет подключение асинхронного клиента (клиент async websocket).
На этот раз я хотел бы поделиться знаниями и представить меня на Websocket Protocol с открытым исходным кодом AioweBocket.
Протокол WebSocket и знания
WebSocket — это протокол для полнодуплексной связи по одному TCP-соединению. Протокол связи WebSocket был установлен IETF в качестве стандарта RFC 6455 в 2011 году и дополнен RFC7936. WebSocket API также является стандартом W3C.
WebSocket упрощает обмен данными между клиентом и сервером, позволяя серверу активно передавать данные клиенту. В API WebSocket браузеру и серверу нужно выполнить только одно рукопожатие, и между ними может быть создано постоянное соединение, и может выполняться двусторонняя передача данных.
Зачем нужен веб-сокет
В прошлом многие веб-сайты использовали опросы для реализации технологии push. Опрос — это определенный интервал времени (например, каждую 1 секунду), когда браузер отправляет HTTP-запрос на сервер, а затем сервер возвращает последние данные в браузер клиента. У этого традиционного режима есть очевидные недостатки, то есть браузеру необходимо постоянно отправлять запросы на сервер, однако HTTP-запрос может содержать длинный заголовок, а действительно достоверных данных может быть лишь малая часть, что явно расточительно. много пропускной способности и других ресурсов. Относительно новой технологией для создания эффекта опроса является Comet. Хотя эта технология может обмениваться данными в двух направлениях, она по-прежнему требует повторных запросов. Кроме того, в Comet часто используемые длинные ссылки также потребляют ресурсы сервера. В этом случае HTML5 определяет протокол WebSocket, который может лучше экономить ресурсы сервера и пропускную способность, а также обеспечивать больше связи в реальном времени.
В чем преимущества WebSocket
Низкое накладное, высокое своевременность, полная двоичная поддержка, поддержка расширения и лучшего сжатия.
- Меньше затрат на контроль. Заголовки пакетов, используемые для управления протоколом, относительно малы при обмене данными между сервером и клиентом после создания соединения. Без расширений этот заголовок имеет размер от 2 до 10 байт (в зависимости от длины пакета) для содержимого «сервер-клиент»; для содержимого «клиент-сервер» для этого заголовка требуется дополнительная 4-байтовая маска. Эти накладные расходы значительно снижены по сравнению с HTTP-запросом, каждый раз содержащим полный заголовок.
- Более высокая производительность в реальном времени. Поскольку протокол является полнодуплексным, сервер может активно отправлять данные клиенту в любое время. По сравнению с HTTP-запросами, которые должны ждать, пока клиент инициирует запрос для ответа сервера, задержка значительно меньше; даже по сравнению с длительным опросом, таким как Comet, он может доставлять данные больше раз за короткий период времени. Оставайся на связи. В отличие от HTTP, Websocket необходимо сначала создать соединение, что делает его протоколом с отслеживанием состояния, а затем некоторая информация о состоянии может быть опущена при обмене данными. И HTTP-запросам может потребоваться нести информацию о состоянии (например, аутентификацию и т. д.) в каждом запросе.
- Лучшая поддержка двоичного кода. Websocket определяет бинарные фреймы, которые упрощают обработку бинарного контента, чем HTTP.
- Расширения могут поддерживаться. Websocket определяет расширения, и пользователи могут расширять протокол и реализовывать некоторые пользовательские подпротоколы. Например, некоторые браузеры поддерживают сжатие и т. д.
- лучшее сжатие. По сравнению со сжатием HTTP, при надлежащей поддержке расширений Websocket может продолжать использовать контекст предыдущего контента и может значительно улучшить степень сжатия при передаче аналогичных данных.
Что случилось с рукопожатием?
WebSocket — это независимый протокол, построенный поверх TCP.
Рукопожатия Websocket через код состояния 101 протокола HTTP/1.1.
Чтобы создать соединение через веб-сокет, через браузер делается запрос, и сервер отвечает, процесс, обычно называемый «рукопожатием».
Спецификация протокола WebSocket
WebSocket — это протокол связи, в котором указаны некоторые спецификации и стандарты. Его стандартом протокола является RFC 6455, а конкретное содержание протокола можно найти вtools.ietf.orgПосмотреть в.
Протокол имеет 14 частей, включая протокол фона и введение, рукопожатие, философию дизайна, конвенции терминологии, двойные требования, маски и закрытие соединения.
Двухтерминальный процесс взаимодействия
Процесс взаимодействия между клиентом и сервером выглядит следующим образом:
Клиент - инициирует запрос установления связи - сервер возвращает информацию после получения запроса - соединение успешно установлено - обмен сообщениями
Итак, первая проблема, которую необходимо решить, — это проблема рукопожатия.
Рукопожатие - Клиент
Что касается стандарта рукопожатия, объясняется в Соглашении:
The opening handshake is intended to be compatible with HTTP-based server-side software and intermediaries, so that a single port can be used by both HTTP clients talking to that server and WebSocket clients talking to that server. To this end, the WebSocket client's handshake is an HTTP Upgrade request:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
In compliance with [RFC2616], header fields in the handshake may be sent by the client in any order, so the order in which different header fields are received is not significant.
Рукопожатие Websocket не использует протокол WebSocket, а протокол HTTP. Запрос, отправленный во время рукопожатия, можно назвать запрос на обновление. Клиент проходит в фазе рукопожатия:
Upgrade: websocket
Connection: Upgrade
Два поля заголовка Connection и Upgrade информируют сервер о том, что необходимо преобразовать протокол связи в веб-сокет. Два поля заголовка Sec-WebSocket-Version и Sec-WebSocket-Protocol указывают версию связи и соглашение о протоколе, а Sec-WebSocket-Key используется как гарантия предотвращения неспровоцированных подключений (на самом деле гарантии нет, поскольку значение ключа полностью определяется контролем на стороне клиента, на стороне сервера нет механизма аутентификации), а несколько других полей заголовка связаны с HTTP. Соглашение работает так же.
Рукопожатие — сервер
Только что клиент только отправил HTTP-запрос, указав, что хочет пожать руку.Серверу необходимо проверить информацию.После подтверждения рукопожатие успешно (соединение установлено успешно, и возможна двусторонняя связь), и тогда сервер ответит клиенту: "Младший брат привет, внутреннего призрака нет, соединение установлено!"
На что должен ответить сервер?
Status Code: 101 Web Socket Protocol Handshake
Sec-WebSocket-Accept: T5ar3gbl3rZJcRmEmBT8vxKjdDo=
Upgrade: websocket
Connection: Upgrade
Во-первых, сервер выдаст код состояния.Код состояния 101 указывает, что сервер понял запрос клиента, и отвечает Connection и Upgrade, чтобы указать, что он переключился на протокол веб-сокета. Sec-WebSocket-Accept — это ключ Sec-WebSocket, подтвержденный сервером и зашифрованный.
Таким образом, клиент и сервер для завершения операции рукопожатия, согласились использовать WebSocket Protocol.
Вы приходите и уходите - обмен данными
После успешного рукопожатия и подтверждения соглашения они могут отправлять информацию друг другу. Как отправляются их сообщения? Это:
client: Hello, server boy
server: Hello, client girl
Это то же самое, что мы отправляем сообщения в WeChat и QQ?
Хотя информация, которую мы видим, такая, в процессе передачи она не такая. Существуют также соответствующие правила передачи этого:
In the WebSocket Protocol, data is transmitted using a sequence of frames. To avoid confusing network intermediaries (such as intercepting proxies) and for security reasons that are further discussed in Section 10.3, a client MUST mask all frames that it sends to the server (see Section 5.3 for further details). (Note that masking is done whether or not the WebSocket Protocol is running over TLS.) The server MUST close the connection upon receiving a frame that is not masked. In this case, a server MAY send a Close frame with a status code of 1002 (protocol error) as defined in Section 7.4.1. A server MUST NOT mask any frames that it sends to the client. A client MUST close a connection if it detects a masked frame. In this case, it MAY use the status code 1002 (protocol error) as defined in Section 7.4.1. (These rules might be relaxed in a future specification.)
The base framing protocol defines a frame type with an opcode, a payload length, and designated locations for "Extension data" and "Application data", which together define the "Payload data". Certain bits and opcodes are reserved for future expansion of the protocol.
Протокол предусматривает, что передача осуществляется не напрямую с использованием кодировки Unicode, а с использованием фреймов.Протокол фреймов данных определяет тип фрейма с кодами операций, длину полезной нагрузки и указанное местоположение «расширенных данных» и приложений.данные», которые вместе определяют "данные полезной нагрузки". Определенные биты и коды операций зарезервированы для будущих расширений протокола.
Формат фрейма данных показан на рисунке:
Рамка состоит из следующих компонентов: FIN, RSV1, RSV2, RSV3, OPCODE, MASK, Длина полезной нагрузки, маскировка-ключ, данные полезных нагрузок. Их смысл и роль следующие:
1.FIN: 1 бит
0:不是消息的最后一个分片
1:是消息的最后一个分片
2. RSV1, RSV2, RSV3: 1 бит каждый
Обычно все 0. Когда клиент и сервер согласовывают расширение WebSocket, эти три флага могут быть ненулевыми, а значение значения определяется расширением. Если присутствует ненулевое значение и расширение WebSocket не используется, подключение завершается ошибкой.
3.Opcode: 4bit
%x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
%x1:表示这是一个文本帧(text frame);
%x2:表示这是一个二进制帧(binary frame);
%x3-7:保留的操作代码,用于后续定义的非控制帧;
%x8:表示连接断开;
%x9:表示这是一个心跳请求(ping);
%xA:表示这是一个心跳响应(pong);
%xB-F:保留的操作代码,用于后续定义的控制帧。
4.Mask: 1bit
Указывает, следует ли выполнять маскированную операцию XOR над полезными данными.
0:否
1:是
5.Payload length: 7bit or (7 + 16)bit or (7 + 64)bit
Указывает длину полезной нагрузки данных.
0~126:数据的长度等于该值;
126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度;
127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。
6.Masking-key: 0 or 4bytes
当 Mask 为 1,则携带了 4 字节的 Masking-key;
当 Mask 为 0,则没有 Masking-key。
掩码算法:按位做循环异或运算,先对该位的索引取模来获得 Masking-key 中对应的值 x,然后对该位与 x 做异或,从而得到真实的 byte 数据。
Примечание. Маска предназначена не для предотвращения утечки данных, а для предотвращения таких проблем, как атаки с отравлением кэша прокси-сервера, которые существовали в более ранних версиях протокола.
7. Данные о полезной нагрузке: Данные о полезной нагрузке
После того, как терминал получает двойное количество кадров или может быть обработано в соответствии со значением каждой позиции информации, экстрагирующей кадры данных.
маска
Здесь следует отметить, что при отправке данных с клиента на сервер данные нужно маскировать, при отправке данных с сервера на клиент маскировать данные не нужно. Если данные, полученные сервером, не были замаскированы, сервер необходимо отключить. Если Mask равно 1, то ключ маскирования определяется в ключе маскирования, и этот ключ маскирования используется для демаскирования полезной нагрузки данных. Для всех фреймов данных, отправляемых клиентом на сервер, Маска равна 1.
оставайся на связи
Я только что упомянул, что протокол WebSocket является двунаправленной связью, поэтому, как только он будет подключен, он не будет отключен?
На самом деле это так, но сервер не может держать все соединения все время, поэтому сервер обычно отправляет пинг-фрейм клиенту в обычное время, а клиент отвечает пинг-фреймом после получения пинга. frame. , если клиент не отвечает, сервер активно отключается.
Код операции: кадр 0x09 представляет собой Ping, 0x0A представляет собой Pong.
Сводка по изучению протокола WebSocket
Протокол WebSocket написан более стандартно, его легче читать и понимать. До тех пор, пока соблюдаются положения протокола, может быть достигнуто стабильное коммуникационное соединение и передача данных.
дизайн aiowebsocket
На основе изучения протокола я собрал библиотеку асинхронных WebSocket с открытым исходным кодом — aiowebsocket.Ее файловая структура и дизайн классов показаны на следующем рисунке:
aiowebsocket
aiowebsocket — это более быстрый, легкий и гибкий клиент WebSocket, чем библиотека того же типа.Он основан на asyncio и отличается простотой и удобством использования с библиотеками websocket-client и websockets. Это результат моих 7 дней изучения WebSocket и документации Python Stream.
установка и использование
Установка: Как и в случае с другими библиотеками, вы можете установить через pip:pip install aiowebsocket
Вы также можете клонировать на github для локального использования.
Использование: Короткий протокол WebSocket — это WS, который похож на HTTP/HTTPS, но с более безопасным протоколом WSS. Разница в использовании не большая, просто открывайте SSL при создании соединения.
Пример кода протокола ws:
import asyncio
import logging
from datetime import datetime
from aiowebsocket.converses import AioWebSocket
async def startup(uri):
async with AioWebSocket(uri) as aws:
converse = aws.manipulator
message = b'AioWebSocket - Async WebSocket Client'
while True:
await converse.send(message)
print('{time}-Client send: {message}'
.format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), message=message))
mes = await converse.receive()
print('{time}-Client receive: {rec}'
.format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rec=mes))
if __name__ == '__main__':
remote = 'ws://echo.websocket.org'
try:
asyncio.get_event_loop().run_until_complete(startup(remote))
except KeyboardInterrupt as exc:
logging.info('Quit.')
После запуска вы получите следующие результаты:
2019-03-04 15:11:25-Client send: b'AioWebSocket - Async WebSocket Client'
2019-03-04 15:11:25-Client receive: b'AioWebSocket - Async WebSocket Client'
2019-03-04 15:11:25-Client send: b'AioWebSocket - Async WebSocket Client'
2019-03-04 15:11:25-Client receive: b'AioWebSocket - Async WebSocket Client'
Это означает, что клиент успешно подключился к службе и нормально общался.
Пример кода протокола wss:
# 开启 ssl 即可
import asyncio
import logging
from datetime import datetime
from aiowebsocket.converses import AioWebSocket
async def startup(uri):
async with AioWebSocket(uri, ssl=True) as aws:
converse = aws.manipulator
message = b'AioWebSocket - Async WebSocket Client'
while True:
await converse.send(message)
print('{time}-Client send: {message}'
.format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), message=message))
mes = await converse.receive()
print('{time}-Client receive: {rec}'
.format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rec=mes))
if __name__ == '__main__':
remote = 'wss://echo.websocket.org'
try:
asyncio.get_event_loop().run_until_complete(startup(remote))
except KeyboardInterrupt as exc:
logging.info('Quit.')
Результат выполнения аналогичен приведенному выше результату выполнения. Кроме того, aiowebsocket также позволяет настраивать заголовки запросов, которые очень полезны при подключении к некоторым веб-сайтам, которым необходимо проверить информацию о происхождении, агенте пользователя и поле заголовка хоста:
import asyncio
import logging
from datetime import datetime
from aiowebsocket.converses import AioWebSocket
async def startup(uri, header):
async with AioWebSocket(uri, headers=header) as aws:
converse = aws.manipulator
message = b'AioWebSocket - Async WebSocket Client'
while True:
await converse.send(message)
print('{time}-Client send: {message}'
.format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), message=message))
mes = await converse.receive()
print('{time}-Client receive: {rec}'
.format(time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'), rec=mes))
if __name__ == '__main__':
remote = 'ws://123.207.167.163:9010/ajaxchattest'
header = [
'GET /ajaxchattest HTTP/1.1',
'Connection: Upgrade',
'Host: 123.207.167.163:9010',
'Origin: http://coolaf.com',
'Sec-WebSocket-Key: RmDgZzaqqvC4hGlWBsEmwQ==',
'Sec-WebSocket-Version: 13',
'Upgrade: websocket',
]
try:
asyncio.get_event_loop().run_until_complete(startup(remote, header))
except KeyboardInterrupt as exc:
logging.info('Quit.')
WS: //123.207.167.163: 9010 / ajaxChattest - это бесплатный, открытый тестовый интерфейс подключения Websocket, который проверяет поле заголовка начала во время фазы рукопожатия, а клиентское соединение не допускается, если спецификация не выполнена.
Адрес проекта на гитхабе
https://github.com/asyncins/aiowebsocket
Добро пожаловать в звезду, если вы можете дать предложения или найти ошибки, это будет еще красивее.