15 мая 2023 года "Исходники.РУ" отмечают своё 23-летие!
Поздравляем всех причастных и неравнодушных с этим событием!
И огромное спасибо всем, кто был и остаётся с нами все эти годы!

Главная Форум Журнал Wiki DRKB Discuz!ML Помощь проекту


Передача сокетов между процессами

Авторы: Warren Young и Frank Schmied

Для того, чтобы передать сокет от одного процесса другому, можно воспользоваться функцией WSADuplicateSocket() из Winsock 2. Изначально в часто задаваемых вопросах (FAQ) эту проблему решали следующим способом:

    Спецификация данного метода подробно описывается в секции 2.10 MSDN-а, где подробно по шагам комментируется данная функция. Так же можно почитать статью Q150523 в Microsoft Knowledge Base, в которой описываются различия наследования сокета в разных версиях Windows.

    Другая забавная особенность Win32 API заключается в том, что он позволяет присваивать новому процессу (во время его создания) различные "стандартные обработчики" ("standard handles") (stdin, stdout и stderr). Статья Q190351 в MSKB описывает данный момент. Обратите внимание, что данная возможность доступна только для дочерних процессов; т.е. Вы не сможете перенаправить стандартный обработчик I/O на сокет. Естевственно, что данная возможность не предоставляет нам таких преимуществ, как, например, Unix-функция dup2().

Так же в FAQ сказано, что мы не можем использовать такую возможность в Winsock 1.1. Однако Frank Schmied показал, что можно слегка обмануть стек Microsoft Winsock 1.1 в Win32, и в конечном результате добиться своей цели. Вот его комментарии:

    Можно заставить Winsock 1.1 передавать сокет от одного процесса другому используя Win32 функцию DuplicateHandle(). Обработка данного вызова может быть весьма сложной. Фактически, генерация реальных дескрипторов процесса не так проста, как может показаться на первый взгляд. Windows использует два типа дескрипторов окна: псевдо-дескрипторы и реальные дескрипторы. Обычно дескрипторы в Windows - это адреса в памяти, и экземпляр дескриптора является ни чем иным как смещением указателя на код, расположенный в текущем адресном пространстве. Итак, дескриптор процесса HINSTANCE (псевдо или локальный) обычно равен 0x4000000. Передача данного дескриптора от одного процесса к другому не работает. Чтобы получить реальный дескриптор текущего процесса, можно использовать OpenProcess():

          OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    

    Создание дубликата дескриптора выглядит примерно так:

           SOCKET ConvertProcessSocket(SOCKET oldsocket, DWORD source_pid)
             {
                 HANDLE source_handle = OpenProcess(PROCESS_ALL_ACCESS,
                         FALSE, source_pid);
                 HANDLE newhandle;
                 DuplicateHandle(source_handle, (HANDLE)oldsocket,
                         GetCurrentProcess(), &newhandle, 0, FALSE,
                         DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS);
                 CloseHandle(source_handle);
                 return (SOCKET)newhandle;
             }
    

    Данный пример отлично работает на многопроцессорном вебсервере. Данная функция передаёт сокет в новый процесс и закрывает дескриптор старого процесса. Дескриптор имеет те же свойства что и старый, но не может быть унаследован дочерним процессом. Чтобы исправить это, достаточно в DuplicateHandle() изменить FALSE на TRUE. Как мы видим, дескриптор основного процесса может быть псевдо-дескриптором, но дескриптор второго процесса обязан быть реальным.

Алгоритм таков: исходный процесс конвертирует локальный дескриптор SOCKET в реальный дескриптор при помощи OpenProcess(), затем передаёт это значение и ID процесса другому процессу. Второй процесс вызывает функцию ConvertProcessSocket(), чтобы преобразовать реальный дескриптор в локальный, который уже можно будет использовать в Winsock. Обратите внимание, что вызов DuplicateHandle() закрывает дескриптор первого процесса, а затем функция CloseHandle() закрывает реальный дескриптор, который вы передаёте второму процессу.

Недостатки: данная методика скорее всего работает только со стеком Microsoft. Однозначно не будет работать в Win16, и, возможно в WinCE. Не извесно, будет или нет работать в присутствие Layered Service Providers, исключая Windows NT 4.0 SP 4+, в которой пропатчен уровень Installable FileSystems (IFS). Возможно найдутся ещё причины, по которым данный метод может не сработать :)