TCP-соединение однозначно идентифицируется (локальный IP-адрес, локальный порт, удаленный IP-адрес, удаленный порт). Это означает, что вполне возможно использовать одну и ту же пару (локальный IP-адрес, локальный порт) для нескольких сокетов, подключающихся к разным удаленным конечным точкам. Предположим, вы хотите сделать http-запрос к «site1.com» и «site2.com». Вы используете сокеты со следующим кодом:
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) {
socket.Bind(new IPEndPoint(IPAddress.Parse("some local ip"), 45455));
socket.Connect(server, port);
socket.Send(someBytes);
// ...
}
Таким образом, вы привязываете сокет к определенной локальной конечной точке с портом 45455. Если вы сейчас попытаетесь сделать это одновременно, чтобы сделать запросы к «site1.com» и «site2.com», вы получите исключение «адрес уже используется».
Но если вы добавите опцию ReuseAddress
(обратите внимание, что это не вариант вашего вопроса) перед привязкой:
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
Вы сможете привязываться к сокетам к одному и тому же локальному (ip, порт) и увидите в netstat два УСТАНОВЛЕННЫХ соединения.
Все вышеизложенное должно показать, что теоретически ничто не мешает повторно использовать даже эфемерный порт для создания нескольких подключений к разным удаленным конечным точкам. Но, когда вы привязываетесь к эфемерному порту (0
) — еще неизвестно, к какой удаленной конечной точке вы собираетесь подключаться. Предположим, что все эфемерные порты используются, и вы привязываетесь к 0. ОС предоставляет вам некоторый порт для повторного использования на этапе привязки, есть один сокет, использующий этот порт, уже подключенный к «site1.com». Вы также пытаетесь подключиться к «site1.com», и это не удается (поскольку все 4 значения, идентифицирующие соединение tcp, одинаковы для обоих сокетов).
Что делает SO_REUSE_UNICASTPORT
, так это откладывает выбор эфемерного порта, когда вы привязываетесь к 0, до фактической фазы соединения (например, вызов Connect()
). На этом этапе (в отличие от привязки) вы уже знаете локальный ip, удаленный ip, удаленный порт, к которому вы собираетесь подключиться, и вам нужно выбрать эфемерный порт. Предположим, что все порты заняты. Теперь вместо того, чтобы выбирать какой-то случайный порт для повторного использования (что может привести к сбою позже при подключении), вы можете выбрать порт, который подключен к другой удаленной конечной точке (отличной от той, к которой пытается подключиться текущий сокет).
Вы можете подтвердить это, например, на этом Статья службы поддержки MS:
SO_REUSE_UNICASTPORT
Чтобы сценарий подключения был реализован, параметр сокета должен быть установлен до привязки сокета. Этот параметр указывает системе отложить выделение портов до времени соединения, когда будет известен 4-кортеж (четверка) для соединения.
Обратите внимание, что SO_REUSE_UNICASTPORT
влияет только на явные привязки (как указано в цитате вашего вопроса, но все же стоит повторить). Если вы привязываетесь неявно (например, когда вы просто Connect()
без привязки) - этот параметр уже установлен по умолчанию (конечно, там, где он поддерживается).
О том, какое влияние это оказывает на ваше конкретное приложение. Во-первых, из вышеизложенного должно быть ясно, что если ваше приложение делает множество запросов к одной и той же удаленной конечной точке (например, к одному и тому же http-серверу) - эта опция не будет иметь никакого эффекта. Однако, если вы делаете много запросов к разным конечным точкам, это должно помочь предотвратить исчерпание портов. Я думаю, что эффект самого ServicePointManager.ReusePort
зависит от того, как HttpClient
работает с сокетами внутри. Если это просто Connect()
без явной привязки - эта опция должна быть включена (в поддерживаемых системах) по умолчанию, поэтому установка ServicePointManager.ReusePort
на true
не будет иметь дополнительного эффекта, в противном случае он будет. Поскольку вы не знаете (и не должны полагаться) на его внутреннюю реализацию, стоит включить ServicePointManager.ReusePort
в вашем конкретном сценарии.
Вы также можете выполнить тесты с этой опцией вкл\выкл, ограничив диапазон эфемерных портов (командой вроде netsh int ipv4 set dynamicport tcp
) небольшими количествами и посмотреть, как пойдет.
person
Evk
schedule
29.10.2017
System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted
, и запускnestat -a -o
на машине с этим исключением показал, что используются многочисленные эфемерные порты. - person Allon Guralnek   schedule 09.08.2018