Полнодуплексный поток и обратное давление в SOCKS-ретрансляторе
Проектируйте поток ретранслятора вокруг независимых байтовых потоков, частичного I/O, ограниченных буферов и обратного давления, а не угаданных размеров ответа.
Рукопожатие SOCKS прошло успешно. Теперь ретранслятор читает от клиента, пишет в пункт назначения, ждёт полного ответа и только потом снова читает от клиента. Такая последовательность может заблокировать совершенно корректное приложение.
После установления TCP-туннеля SOCKS не определяет сообщения внутри него. Пункт назначения может отправить первым. Обе стороны могут отправлять независимо. Одна сторона может отправить короткую запись и затем ждать ответа другой.
Передавайте байты, а не угаданные транзакции
Вызов приёма возвращает доступные байты в соответствии с флагами и состоянием сокета. Он не сообщает обычному ретранслятору, что ответ приложения завершён. Ожидание заполнения произвольно большого буфера может помешать передаче небольшого сообщения, необходимого партнёру для продолжения.
Используйте существующую модель конкурентности приложения для обслуживания обоих направлений: цикл событий, асинхронный I/O или другую намеренно выбранную схему. Важно то, что прогресс в одном направлении не требует придуманной границы сообщения в другом.
Держите два ограниченных ожидающих буфера
Данные, прочитанные от клиента, могут всё ещё ожидать доставки в пункт назначения, а данные из пункта назначения ожидают доставки клиенту. Отслеживайте эти ожидающие байты отдельно.
Отправка может записать меньше байтов, чем запрошено. Сохраняйте не записанную часть; не отбрасывайте её и не воспроизводите весь буфер заново. Временно недоступный для записи сокет — причина ждать правильного события готовности, а не крутиться непрерывно.
Ограничьте буферы. Если пункт назначения медленный, продолжение чтения неограниченных данных клиента просто переносит очередь в память ретранслятора. Обратное давление означает приостановку чтения, когда соответствующий ожидающий буфер заполнен, и возобновление, когда записи освобождают место.
Завершение в одном направлении требует политики
TCP может закрыть направление отправки, пока другое направление ещё содержит полезные данные. Решите, как распространять это состояние после опустошения буферизованных байтов, и отличайте упорядоченное закрытие от ошибки или тайм-аута.
Ничто из этого не требует, чтобы ретранслятор расшифровывал или разбирал полезную нагрузку приложения. Ему нужна корректная обработка потока. Одно лишь увеличение размера буфера не исправит ретранслятор, который ждёт не ту сторону или предполагает, что каждое чтение соответствует одному целому запросу.