Как поменять местами два открытых файловых дескриптора? - PullRequest
0 голосов
/ 09 января 2019

Для моего магистерского проекта я создаю API на C, который работает с сокетами Unix. Короче говоря, у меня есть два сокета, идентифицированные их двумя fds, на которых я назвал O_NONBLOCK connect(). На данный момент я звоню select(), чтобы проверить, какой из них подключается первым и готов к записи.

Проблемы начинаются сейчас, поскольку приложение, использующее этот API, знает только об одном из этих сокетов, скажем, о том, который идентифицирован fd1. Если сокет, идентифицированный с помощью fd2, подключается первым, приложение не может знать, что оно может записать в этот сокет.

Я думаю, что мои лучшие варианты использования dup() и / или dup2(), но согласно их справочной странице, dup() создает копию fd, переданную функции, но которая ссылается на тот же открытый файл описание, означающее, что оба могут использоваться взаимозаменяемо, и dup2() закрывает новый fd, который заменяет старый fd.

Итак, мои предположения о том, что произойдет (в псевдокоде)

int fd1, fd2, fd3;

fd1 = socket(x); // what the app is aware of
fd2 = socket(y); // first to connect

fd3 = dup(fd1); // fd1 and fd3 identify the same description
dup2(fd2, fd1); // The description identified by fd2 is now identified by fd1, the description previously identified by fd1 (and fd3) is closed
dup2(fd3, fd2); // The description identified by fd3 (copy of fd1, closed in the line above) is identified by fd2 (which can be closed and reassigned to fd3) since now the the description that was being identified by fd2 is being identified by fd1.

Что выглядит хорошо, за исключением того факта, что первый dup2() закрывает fd1, который закрывает также fd3, поскольку они идентифицируют одно и то же описание файла. Второй dup2() работает нормально, но он заменяет fd соединения, которое было закрыто первым, пока я хочу, чтобы оно продолжало пытаться соединиться.

Может ли кто-нибудь с лучшим пониманием файловых дескрипторов Unix помочь мне?

РЕДАКТИРОВАТЬ: Я хочу немного подробнее рассказать о том, что делает API и почему приложение видит только один fd.

API предоставляет приложению средства для вызова очень «модной» версии connect() select() и close().

Когда приложение вызывает api_connect(), оно передает функции указатель на int (вместе со всеми необходимыми адресами, протоколами и т. Д.). api_connect() вызовет socket(), bind() и connect(), важной частью является то, что он запишет возвращаемое значение socket() в память, проанализированную через указатель. Это то, что я имею в виду под «сокетом известно только об одном fd». Затем приложение вызовет FD_SET(fd1, write_set), вызовет api_select () и затем проверит, доступен ли для записи fd, вызвав FD_ISSET(fd1, write_set). api_select() работает более или менее подобно select(), но имеет таймер, который может инициировать тайм-аут, если для подключения требуется больше установленного времени подключения (так как оно O_NONBLOCK). Если это происходит, api_select() создает новое соединение на другом интерфейсе (вызывая все необходимые socket(), bind() и connect()). Это соединение идентифицируется новым fd -fd2-, о котором приложение не знает, и которое отслеживается в API.

Теперь, если приложение вызывает api_select() с FD_SET(fd1, write_set) и API понимает, что это второе соединение, которое завершилось, что делает запись доступной для fd2, я хочу, чтобы приложение использовало fd2. Проблема в том, что приложение будет вызывать только FD_ISSET(fd1, write_set) и write(fd1) после этого, поэтому мне нужно заменить fd2 на fd1.

На данный момент я действительно запутался в том, действительно ли мне нужно дублировать или просто выполнить целочисленный обмен (мое понимание дескрипторов файлов Unix немного больше базового).

1 Ответ

0 голосов
/ 09 января 2019

Я думаю, что мои лучшие варианты используют dup() и / или dup2(), но в соответствии на их справочную страницу, dup() создает копию файла, переданного в функция, но ссылающаяся на то же описание открытого файла,

Да.

значение что эти два могут быть использованы взаимозаменяемо,

Может быть. Это зависит от того, что вы подразумеваете под "взаимозаменяемо".

и dup2() закрывает новый fd который заменяет старый FD.

dup2() закрывает дескриптор файла target , если он открыт, перед дублированием дескриптора источника на него. Возможно, это то, что вы имели в виду, но мне трудно читать ваше описание таким образом.

Итак, мои предположения о том, что произойдет, (извините за мой дерьмовый псевдо код)

int fd1, fd2, fd3;

fd1 = socket(x); // what the app is aware of
fd2 = socket(y); // first to connect

fd3 = dup(fd1); // fd1 and fd3 indentify the same description

Пока хорошо.

dup2(fd2, fd1); // The description identified by fd2 is now identified by fd1, the description previously identified by fd1 (and fd3) is closed

Нет, комментарий неверный. Файловый дескриптор fd1 сначала закрывается, а затем делается дубликатом fd2. Базовое описание открытого файла, на которое первоначально ссылался fd1, не закрыто, потому что с процессом связан другой дескриптор открытого файла, fd3.

dup2(fd3, fd2); // The description identified by fd3 (copy of fd1, closed in the line above) is identified by fd2 (which can be closed and reassigned to fd3) since now the thescription that was being identified by fd2 is being identified by fd1.

Что выглядит нормально, за исключением того, что первый dup2() закрывается fd1,

Да, это так.

, который закрывает также fd3

Нет, это не так.

, поскольку они идентифицируют один и тот же файл описание.

Ненужные. Закрытие - это функция дескрипторов файлов, а не непосредственное описание базовых открытых файлов. На самом деле, было бы лучше не использовать здесь слово «идентификация», поскольку это предполагает, что файловые дескрипторы являются своего рода идентификатором или псевдонимом для описания открытого файла. Они не. Файловые дескрипторы идентифицируют записи в таблице ассоциаций с открытыми описаниями файлов, но сами по себе не являются открытыми описаниями файлов.

Короче говоря, ваша последовательность вызовов dup(), dup2() и dup2() должна производить именно тот обмен, который вы хотите, при условии, что все они будут успешными. Однако они оставляют дополнительный открытый файловый дескриптор, который может привести к утечке файлового дескриптора при многих обстоятельствах. Поэтому не забудьте закончить с

close(fd3);

Конечно, все, что предполагает, что это значение из fd1, которое является особенным для приложения, а не переменная , содержащая его . Файловые дескрипторы - это просто числа. В объектах, которые их содержат, нет ничего особенного, поэтому если это переменная fd1, которую необходимо использовать приложению, независимо от его конкретного значения, то все, что вам нужно сделать, - это выполнить обычный обмен целых чисел:

fd3 = fd1;
fd1 = fd2;
fd2 = fd3;

Относительно редактирования , вы пишете,

Когда приложение вызывает api_connect(), оно переходит к функции a указатель на int (вместе со всеми необходимыми адресами и протоколы и т. д.). api_connect () вызовет socket (), bind () и connect (), важной частью является то, что он напишет возвращаемое значение socket () в памяти разбирается через указатель.

Возвращает ли api_connect() значение дескриптора файла, записывая его через указатель или передавая его как или в возвращаемом значении функции, не имеет значения. Дело в том, что значение имеет , а не объект, если таковой имеется, который его содержит.

Это то, что я означает "сокет знает только об одном fd". Приложение будет затем позвоните FD_SET(fd1, write_set), позвоните api_select() и затем проверьте если fd доступен для записи с помощью вызова FD_ISSET(fd1, write_set).

Ну, это звучит проблематично в свете остальной части вашего описания.

[При некоторых условиях] api_select() создает новое соединение на другом интерфейсе (вызывая все необходимые функции socket (), bind () и connect ()). это соединение идентифицируется новым fd -fd2- приложение незнать, и который отслеживается в API.

Теперь, если приложение вызывает api_select() с FD_SET(fd1, write_set) и API понимает, что это второе соединение, которое завершено, таким образом, делая fd2 доступным для записи, я хочу, чтобы приложение использовало fd2. проблема в том, что приложение будет вызывать только FD_ISSET(fd1, write_set) и write(fd1) после этого, поэтому мне нужно заменить fd2 с fd1.

Обратите внимание, что даже если вы выполните обмен файловыми дескрипторами, как описано в первой части этого ответа, это не повлияет на членство FD в любом fd_set, так как такое членство логическое , не физический. Вам придется управлять членством fd_set вручную, если вызывающий абонент использует это.

Мне неясно, предназначен ли api_select() для предоставления услуг для более чем одного (указанного вызывающим абонентом) файлового дескриптора одновременно, как это может сделать select(), но я полагаю, что для этого требуется бухгалтерия это было бы чудовищно. С другой стороны, если на самом деле функция одновременно обрабатывает только один FD, предоставляемый вызывающим абонентом, то имитация интерфейса select() будет ... нечетной.

В этом случае я настоятельно призываю вас разработать более подходящий интерфейс. Помимо прочего, такой интерфейс должен обсуждать вопрос об обмене FD. Вместо этого он может напрямую сообщить вызывающей стороне, какой FD, если таковой имеется, готов к использованию, либо возвращая его, либо записывая его через указатель на переменную, указанную вызывающей стороной.

Кроме того, в случае, если вы, так или иначе, переключаетесь на альтернативный FD, не забывайте об управлении старым, чтобы не пропустить дескриптор файла. Каждый процесс имеет довольно ограниченное количество доступных, поэтому утечка файлового дескриптора может быть гораздо более сложной, чем утечка памяти. В случае, если вы действительно переключаетесь, вы уверены, что вам действительно нужно поменяться, в отличие от простого dup2() добавления нового FD на старый, а затем закрытия нового?

...