У меня есть прокси-сервер HTTP, который действует как посредник.Это в основном делает следующее:
- Прослушивание запроса клиента-браузера
- Пересылка запроса на сервер
- Разбор ответа сервера
- Пересылкаответ обратно на клиент-браузер
Таким образом, в основном есть один NetworkStream
, или даже чаще SslStream
между клиентом-браузером и прокси, и еще один между прокси иserver.
Возникло также требование пересылать трафик WebSocket между клиентом и сервером.
Итак, теперь, когда клиент-браузер запрашивает обновление соединения до websocket, а удаленный сервер отвечает:HTTP-код 101, прокси-сервер поддерживает эти соединения для пересылки дальнейших сообщений от клиента к серверу и наоборот.
Таким образом, после того, как прокси-сервер получил сообщение от удаленного сервера о том, что он готов переключать протоколы, оннеобходимо войти в цикл, где клиентские и серверные потоки опрашиваются для данных, и где любые полученные данные пересылаются вдругая сторона.
Проблема
WebSocket позволяет обеим сторонам отправлять сообщения в любое время.Это особенно проблема с управляющими сообщениями, такими как пинг / понг, где любая сторона может отправить пинг в любое время, а другая сторона должна ответить понг своевременноманера.Теперь рассмотрим два экземпляра SslStream
, у которых нет свойства DataAvailable
, где единственный способ прочитать данные - вызвать Read
/ ReadAsync
, который может не вернуться, пока не будут доступны некоторые данные.Рассмотрим следующий псевдокод:
public async Task GetMessage()
{
// All these methods that we await read from the source stream
byte[] firstByte = await GetFirstByte(); // 1-byte buffer
byte[] messageLengthBytes = await GetMessageLengthBytes();
uint messageLength = GetMessageLength(messageLengthBytes);
bool isMessageMasked = DetermineIfMessageMasked(messageLengthBytes);
byte[] maskBytes;
if (isMessageMasked)
{
maskBytes = await GetMaskBytes();
}
byte[] messagePayload = await GetMessagePayload(messageLength);
// This method writes to the destination stream
await ComposeAndForwardMessageToOtherParty(firstByte, messageLengthBytes, maskBytes, messagePayload);
}
Приведенный выше псевдокод читает из одного потока и записывает в другой.Проблема заключается в том, что вышеуказанную процедуру нужно запускать для обоих потоков одновременно, потому что мы не знаем, какая сторона отправит сообщение другой в любой данный момент времени.Однако невозможно выполнить операцию записи, пока активна операция чтения.А поскольку у нас нет средств, необходимых для опроса входящих данных, операции чтения должны блокироваться.Это означает, что если мы начнем операции чтения для обоих потоков одновременно, мы можем забыть о записи в них.Один поток в конечном итоге вернет некоторые данные, но мы не сможем отправить эти данные в другой поток, так как он все еще будет занят попыткой чтения.И это может занять некоторое время, по крайней мере, пока сторона, владеющая этим потоком, не отправит запрос ping .