Как поменять местами два открытых файловых дескриптора?

Для моего магистерского проекта я создаю API на C, который работает с сокетами Unix. Короче говоря, у меня есть два сокета, идентифицированные их двумя fds, на которых я назвал O_NONBLOCKconnect(), На данный момент я звоню 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 ответ

Решение

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

Да.

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

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

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

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

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

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(), он передает функции указатель на 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 на старый, а затем закрыть новый?

Другие вопросы по тегам