Как реализован протокол WebSocket
Что проверяют
Знаете ли вы, что происходит на уровне протокола, а не только как вызвать ClientWebSocket. Следом часто спрашивают, чем WebSocket отличается от SSE и long polling и когда вы бы взяли SignalR.
Как отвечать
WebSocket описан в RFC 6455. Соединение начинается как обычный HTTP/1.1 запрос с заголовками Upgrade: websocket, Connection: Upgrade, Sec-WebSocket-Key и Sec-WebSocket-Version: 13. Сервер отвечает 101 Switching Protocols и возвращает Sec-WebSocket-Accept: SHA-1 от ключа, склеенного с фиксированным GUID, в Base64. Это защита от случайных ответов, а не шифрование. Дальше в том же TCP-соединении идёт полнодуплексный обмен кадрами:
- кадры: у каждого есть бит
FIN, опкод (текст, бинарные данные, продолжение,close,ping,pong) и длина. Одно сообщение может прийти несколькими кадрами; - маскирование: кадры от клиента к серверу обязательно маскируются 4-байтовым ключом, от сервера нет. Это защищает промежуточные прокси от отравления кэша;
- keep-alive:
pingиpongдержат соединение живым через NAT и балансировщики; - закрытие: обмен кадрами
closeс кодом, например 1000 для нормального завершения; - TLS: схема
wss://это WebSocket поверх TLS, в продакшене используют только её.
В .NET клиентом служит ClientWebSocket, на сервере в ASP.NET Core подключается app.UseWebSockets(), а соединение принимается через HttpContext.WebSockets.AcceptWebSocketAsync(). На практике чаще берут SignalR: он даёт группы, переподключение, масштабирование через Redis backplane и откатывается на SSE или long polling, если WebSocket недоступен.
Где спотыкаются
Называют WebSocket полудуплексным. Он полнодуплексный: обе стороны пишут одновременно и независимо.
Читают сообщение одним ReceiveAsync в буфер на 1024 байта. Нужно крутить цикл, пока result.EndOfMessage не станет true, иначе длинное сообщение обрежется.
Забывают, что у ClientWebSocket одновременно допустимы только одна отправка и один приём. Параллельные SendAsync из разных потоков ломают соединение, поэтому отправку сериализуют через Channel или SemaphoreSlim.