Linux сокет это файл

Типы файлов в Linux

Философия операционных систем Unix гласит, что все есть файл. Это значит, что вся работа с этой операционной системой сводится к файлам. Поскольку Linux можно считать тоже потомком Unix, то эта концепция применима и здесь. Файлы это объекты, в которые мы записываем информацию и наши данные, исполняемые файлы, но кроме этих привычных нам понятий здесь есть файлы специального назначения — файлы устройств, файлы туннелей, сокетов и многое другое. Эта тема очень слабо освещена в интернете, поэтому в нашей сегодняшней статье мы рассмотрим типы файлов linux.

Пространство нашего жесткого диска занято файлами разных типов. Например, взять даже наш корневой раздел (/), при создании файла, файловая система записывает его в определенном формате на нужное физическое место жесткого диска. Всегда, для работы с файлами используется файловая система, но не всегда она записывает файлы на диск, файловая система может работать на лету, генерируя файлы, например, как procfs, с помощью которой может быть выполнена настройка ядра linux или записывать файлы в оперативную память, как tmpfs расположенная в папке /tmp. Но все это не имеет значения, ведь в любом случае мы имеем дело именно с файлами.

В системе Linux нет различий между каталогами и файлами. Но каталоги могут объединять другие файлы в группы, чтобы их было легче найти и использовать. Все аппаратные устройства представлены в виде файлов и находятся в каталоге dev, только через эти файлы программы могут работать с ними.

Преимущество использования файлов как для обычной информации, так и для устройств, в том, что не нужно реализовать отдельный набор API интерфейсов для каждого устройства, с ним могут работать все стандартные утилиты Linux и API интерфейсы.

Типы файлов в Linux

Файлы в операционной системе Linux можно поделить на три основных типа:

  • Обычные файлы, для хранения информации
  • Специальные файлы — для устройств и туннелей
  • Директории

Дальше рассмотрим более подробно эти типы файлов linux.

Обычные файлы

Это файлы, с которыми мы привыкли работать каждый день, они могут содержать текст, исполняемые инструкции для программ, изображения или другую информацию. Это самый распространенный тип файлов, которые вы можете найти в системе Linux. Рассмотрим небольшой список относящихся сюда файлов:

  • Текстовые файлы
  • Исполняемые файлы
  • Файлы изображений
  • Файлы архивов
  • Файлы библиотек программ
  • И другие подобные типы

Утилита ls может определять тип файла в режиме списка, обычные файлы обозначаются черточкой, например:

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

Посмотреть форматы файлов linux можно с помощью утилиты file. Например:

Система сообщила что это исполняемый файл, а теперь посмотрим обычный, текстовый:

Утилита умеет распознавать все известные форматы файлов. Чтобы узнать вывести все доступные форматы файлов linux наберите:

Специальные файлы

Специальные файлы намного интереснее, они предназначены для обмена информации с ядром, работы с устройствами или общения между программами. Такие файлы могут тоже быть нескольких типов, в зависимости от назначения.

Блочные файлы — это файлы устройств, которые обеспечивают буферизованный доступ к аппаратным компонентам. При записи данных на жесткий диск или на флешку нет смысла записывать данные сразу же после их поступления. Так мы будем только понапрасну расходовать ресурс устройства и энергию. Можно подождать пока наберется достаточное количество данных а потом записать их за один раз. Эти данные и собираются в буфере. С помощью таких файлов, файловая система и другие утилиты могут обращаться к драйверам аппаратных устройств. Такие файлы могут передать большой блок данных за небольшой один раз.

Утилита ls обозначает блочные файлы буквой b, например, выберем все блочные файлы из каталога /dev:

ls -l /dev/ | grep «^b»

Утилита file, которую мы рассматривали в предыдущем разделе тоже умеет определять типы файлов:

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

Вы также можете отфильтровать их с помощью ls. Символьные файлы обозначаются буквой c (character):

ls -l /dev/ | grep «^c»

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

Но вернемся к символическим ссылкам. Утилита ls обозначает их буквой l (link):

ls -l /dev/ | grep «^l»

Создавать символические ссылки можно с помощью утилиты ln. Например:

ln -s file1.txt file2.txt

Можете использовать ls чтобы убедиться в том, что это ссылка. Проверьте таким же образом жесткие ссылки, чтобы убедиться, что то что я сказал о них — правда.

Читайте также:  Неопознанная сеть общедоступная сеть windows 10

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

Обозначаются такие типы файлов linux буквой p (pipe):

ls -l /dev/ | grep «^p»

Чтобы создать именованный тоннель вы можете использовать утилиту mkfifo:

echo «test test test» > pipe1

После создания туннеля мы передали в него данные, и оболочка стала не интерактивной. Она будет ожидать пока данные будут прочитаны на другом конце туннеля. Открываем другую оболочку и читаем данные:

while read line ;do echo «Data: ‘$line’ «; done

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

Файлы сокетов обозначаются буквой s:

ls -l /dev/ | grep «^s»

Создать сокет можно с помощью функции socket() на языке программирования Си, чтение и запись выполняется системными вызовами read() и write(). Но нам сейчас не нужно писать реальную программу, будет достаточно немного поиграться. Поэтому воспользуемся утилитой nc. Создаем Unix сокет:

nc -lU socket.sock

Подключаемся к нему из другой консоли:

nc -U socket.sock

Все данные, которые вы будете набирать в одной из консолей будут отправляться на другую после нажатия Enter, связь работает в обоих направлениях.

Каталоги

Это специальные файлы, которые позволяют объединять другие и каталоги в группы для более простой навигации и поиска. Естественно, они могут содержать как обычные, так и специальные файлы, одним словом любые типы файлов ос linux. В системе Linux, файлы организуются в папки начиная от корня (/)

Обозначаются каталоги буквой d (directory):

Создать каталог в linux можно с помощью команды mkdir:

Заключение

Теперь у вас более четкое представление о том, почему в linux все является файлами, а также какие типы файлов в linux существуют в вашей системе. Вы можете найти более подробную информацию по каждому виду файлов в интернете, но если у вас остались вопросы, можете задать их в комментариях!

Источник

Linux сокет это файл

В конкурсе на лучшую компьютерную идею всех времен и народов сокеты, без сомнения, могли бы рассчитывать на призовое место. Как и другие средства межпроцессного взаимодействия, рассмотренные в этой серии статей, сокеты впервые были реализованы именно на платформе Unix (4.2BSD), однако, концепция сокетов, как универсального средства обмена данными между процессами, оказалась настолько удачна, что все современные системы поддерживают, по крайней мере, некоторое подмножество сокетов. Причины успеха сокетов заключаются в их простоте и универсальности. Программы, обменивающиеся данными с помощью сокетов, могут работать в одной системе и в разных, используя для обмена данными как специальные объекты системы, так и сетевой стек. Как и каналы, сокеты используют простой интерфейс, основанный на «файловых» функциях read(2) и write(2) (открывая сокет, программа Unix получает дескриптор файла, благодаря которому можно работать с сокетами, используя файловые функции), но, в отличие от каналов, сокеты позволяют передавать данные в обоих направлениях, как в синхронном, так и в асинхронном режиме.

Большинство программистов используют для работы с сокетами различные библиотеки высокого уровня, однако, высокоуровневые библиотеки, как правило, не позволяют задействовать всю мощь и все многообразие сокетов. Наглядный пример многообразия – файловые сокеты. Программистам Windows должны быть знакомы сетевые сокеты, которые обычно организуют обмен данными с помощью протоколов семейства TCP/IP, однако в Unix есть и другие типы сокетов, специально предназначенные для обмена данными между локальными процессами.

Сокеты в файловом пространстве имен

Константы и функции, необходимые для работы с сокетами в файловом пространстве имен, объявлены в файлах и . Как и файлы, сокеты в программах представлены дескрипторами. Дескриптор сокета можно получить с помощью функции socket(2). Первый параметр этой функции – домен, к которому принадлежит сокет. Домен сокета обозначает тип соединения (а не доменное имя Интернета, как вы могли бы подумать). Домен, обозначенный константой AF_UNIX, соответствует сокетам в файловом пространстве имен. Второй параметр socket() определяет тип сокета. значение SOCK_DGRAM указывает датаграммный сокет (я предпочитаю этот вариант написания используемому в [1] «дейтаграммный»). Датаграммные сокеты осуществляют ненадежные соединения при передаче данных по сети и допускают широковещательную передачу данных. Другой часто используемый тип сокетов – SOCK_STREAM соответствует потоковым сокетам, реализующим соединения «точка-точка» с надежной передачей данных. Впрочем, в пространстве файловых имен датаграммные сокеты также надежны, как и потоковые сокеты. Третий параметр функции socket() позволяет указать протокол, используемый для передачи данных. Мы оставляем значение этого параметра равным нулю. В случае ошибки функция socket() возвращает -1.

После получения дескриптора сокета мы вызываем функцию bind(2), которая связывает сокет с заданным адресом (связывать сокет с адресом необходимо в программе-сервере, но не в клиенте). Первым параметром функции является дескриптор, а вторым – указатель на структуру sockaddr (переменная srvr_name), содержащую адрес, на котором регистрируется сервер (третий параметр функции – длина структуры, содержащей адрес). Вместо общей структуры sockaddr для сокетов Unix (сокетов в файловом пространстве имен) можно использовать специализированную структуру sockaddr_un. Поле sockaddr.sa_family позволяет указать семейство адресов, которым мы будем пользоваться. В нашем случае это семейство адресов файловых сокетов Unix AF_UNIX. Сам адрес семейства AF_UNIX (поле sa_data) представляет собой обычное имя файла сокета. После вызова bind() наша программа-сервер становится доступна для соединения по заданному адресу (имени файла).

Читайте также:  Сканер штрих кода для линукс

При обмене данными с датаграммными сокетами мы используем не функции write() и read(), а специальные функции recvfrom(2) и sendto(2). Эти же функции могут применяться и при работе с потоковыми сокетами, но в соответствующем примере мы воспользуемся «сладкой парочкой» read()/write(). Для чтения данных из датаграммного сокета мы используем функцию recvfrom(2), которая по умолчанию блокирует программу до тех пор, пока на входе не появятся новые данные.

Вызывая функцию recvfrom(), мы передаем ей указатель на еще одну структуру типа sockaddr, в которой функция возвращает данные об адресе клиента, запросившего соединение (в случае файловых сокетов этот параметр не несет полезной информации). Последний параметр функции recvfrom() – указатель на переменную, в которой будет возвращена длина структуры с адресом. Если информация об адресе клиента нас не интересует, мы можем передать значения NULL в предпоследнем и последнем параметрах. По завершении работы с сокетом мы закрываем его с помощью «файловой» функции close(). Перед выходом из программы-сервера следует удалить файл сокета, созданный в результате вызова socket(), что мы и делаем с помощью функции unlink().

Если программа-сервер показалась вам простой, то программа-клиент (fsclient.c) будет еще проще. Мы открываем сокет с помощью функции socket() и передаем данные (тестовую строку) серверу с помощью «напарника» recvfrom(), функции sendto(2):

Первый параметр функции sendto() – дескриптор сокета, второй и третий параметры позволяют указать адрес буфера для передачи данных и его длину. Четвертый параметр предназначен для передачи дополнительных флагов. Предпоследний и последний параметры несут информацию об адресе сервера и его длине, соответственно. Если при работе с датаграммными сокетами вызвать функцию connect(2) (см. ниже), то можно не указывать адрес назначения каждый раз (достаточно указать его один раз, как параметр функции connect()). Перед вызовом функции sendto() нам надо заполнить структуру sockaddr (переменную srvr_name) данными об адресе сервера. После окончания передачи данных мы закрываем сокет с помощью close(). Если вы запустите программу-сервер, а затем программу-клиент, то сервер распечатает тестовою строку, переданную клиентом.

Парные сокеты

Парные сокеты создаются функцией socketpair(2). У функции socketpair() четыре параметра. Первые три параметра функции те же, что и у socket(), а четвертым параметром является массив из двух переменных, в которых возвращаются дескрипторы. Дескрипторы сокетов, возвращенные socketpair(), уже готовы к передаче данных, так что мы сразу можем применять к ним функции read()/write(). После вызова fork() каждый процесс получает оба дескриптора, один из которых он должен закрыть. Для закрытия сокета мы используем функцию close().

При взгляде на интерфейс программирования парных сокетов может возникнуть вопрос, а почему собственно эти функции относятся к сокетам? Ведь при работе с ними мы не используем ни адреса, ни модель клиент-сервер. Это верно, но заметьте, что функции socketpair() передаются значения домена и типа сокета, так что и формально, и с точки зрения реализации в системе мы используем настоящие сокеты. Следует отметить, что указание домена в функции socketpair() выглядит явно излишне, поскольку для этой функции система поддерживает только сокеты в домене AF_UNIX (вполне логичное ограничение, если учесть, что парные сокеты не имеют имен и предназначены для обмена данными между родственными процессами).

Сетевые сокеты

Использование сетевых сокетов сделает процесс масштабирования проекта безболезненным. Впрочем, у сетевых сокетов есть и недостатки. Даже если сокеты используются для обмена данными на одной и той же машине, передаваемые данные должны пройти все уровни сетевого стека, что отрицательно сказывается на быстродействии и нагрузке на систему.

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

В первом параметре функции socket() мы передаем константу AF_INET, указывающую на то, что открываемый сокет должен быть сетевым. Значение второго параметра требует, чтобы сокет был потоковым. Далее мы, как и в случае сокета в файловом пространстве имен, вызываем функцию bind():

Переменная serv_addr, — это структура типа sockaddr_in. Тип sockaddr_in специально предназначен для хранения адресов в формате Интернета. Самое главное отличие sockaddr_in от sockaddr_un – наличие параметра sin_port, предназначенного для хранения значения порта. Функция htons() переписывает двухбайтовое значение порта так, чтобы порядок байтов соответствовал принятому в Интернете (см. врезку). В качестве семейства адресов мы указываем AF_INET (семейство адресов Интернета), а в качестве самого адреса – специальную константу INADDR_ANY. Благодаря этой константе наша программа сервер зарегистрируется на всех адресах той машины, на которой она выполняется.

Именно так переводятся на русский язык термины little-endian и big-endian. В компьютерной литературе эти термины обозначают порядок байтов, используемый процессором для представления простых многобайтовых типов (например, 32-битного целого). В оригинале же (то есть в сказочной повести Дж. Свифта «Гулливер в стране лилипутов») так именовались враждебные общественные течения, приверженцы которых придерживались противоположных взглядов на порядок очистки яйца от скорлупы. Разногласия между остроконечниками и тупоконечниками даже стали причинной войны между Лилипутией и враждебным государством Блефуску. Впрочем, в компьютерном мире проблемы порядка байтов могут достигать совсем не лилипутских размеров. На процессорах Intel порядок байтов остроконечный, а, например, в системах MacOS X – Power PC Sun SPARC — тупоконечный (если учесть, что Apple отказалась от PowerPC, а Sun заменяет RISC-архитектуры оптеронами, получится, что остроконечники побеждают). Однако протоколы Интернета используют «тупоконечный» порядок байтов. Для того чтобы избежать путаницы, во всех системах, включая «тупоконечников» рекомендуется использовать функцию htons(). Эта функция «знает» порядок байтов в системе и, если нужно, приводит его в соответствие с принятым в протоколах TCP/IP. В русскоязычном Интернете одно время кочевала статья, в которой утверждалась (впрочем, шутливо), что чуждый для Intel порядок байтов и вызванная этим необходимость в дополнительной операции перестановки являются результатом заговора со стороны гигантских софтверных компаний. В книге [1] используются термины «прямой порядок байтов» для little-endian и «обратный порядок байтов» для big-endian.

Читайте также:  Хороший линукс для дома

Чтобы понять, что мы должны делать дальше, давайте вспомним, как работает сетевая подсистема Unix и, в данном случае, любой другой ОС. Сетевой сервер должен уметь выполнять запросы множества клиентов одновременно (наш сервер netserver.c фактически может обработать запрос только одного клиента, но речь сейчас идет об общем случае). При этом в соединениях «точка-точка», например, при использовании потоковых сокетов, для каждого клиента у сервера должен быть открыт отдельный сокет. Из этого следует, что мы не должны устанавливать соединение с клиентом через сам сокет sock, предназначенный для прослушивания входящих запросов (обычно, при использовании сетевых сокетов мы и не можем этого сделать), иначе все другие попытки соединиться с сервером по указанному адресу и порту будут заблокированы. Вместо этого мы вызываем функцию listen(2), которая переводит сервер в режим ожидания запроса на соединение:

Второй параметр listen() – максимальное число соединений, которые сервер может обрабатывать одновременно. Далее мы вызываем функцию accept(2), которая устанавливает соединение в ответ на запрос клиента:

Получив запрос на соединение, функция accept() возвращает новый сокет, открытый для обмена данными с клиентом, запросившим соединение. Сервер как бы перенаправляет запрошенное соединение на другой сокет, оставляя сокет sock свободным для прослушивания запросов на установку соединения. Второй параметр функции accept() содержит сведения об адресе клиента, запросившего соединение, а третий параметр указывает размер второго. Так же как и при вызове функции recvfom(), мы можем передать NULL в последнем и предпоследнем параметрах. Для чтения и записи данных сервер использует функции read() и write(), а для закрытия сокетов, естественно, close(). В программе-клиенте (netclient.c) нам, прежде всего, нужно решить задачу, с которой мы не сталкивались при написании сервера, а именно выполнить преобразование доменного имени сервера в его сетевой адрес. Разрешение доменных имен выполняет функция gethostbyname():

Функция получает указатель на строку с Интернет-именем сервера (например, www.unix.com или 192.168.1.16) и возвращает указатель на структуру hostent (переменная server), которая содержит имя сервера в приемлемом для дальнейшего использования виде. При этом, если необходимо, выполняется разрешение доменного имени в сетевой адрес. Далее мы заполняем поля переменной serv_addr (структуры sockaddr_in) значениями адреса и порта:

Программа-клиент открывает новый сокет с помощью вызова функции socket() аналогично тому, как это делает сервер (дескриптор сокета, который возвращает socket() мы сохраним в переменной sock), и вызывает функцию connect(2) для установки соединения:

Теперь сокет готов к передаче и приему данных. Программа-клиент считывает символы, вводимые пользователем в окне терминала. Когда пользователь нажимает , программа передает данные серверу, ждет ответного сообщения сервера и распечатывает его.

На протяжении этой статьи мы несколько раз упоминали не-блокирующие сокеты. Остановимся на них немного подробнее. О не-блокирующих сокетах вам нужно знать, прежде всего, то, что ими можно не пользоваться. Благодаря многопоточному (многопрограммному) программированию мы можем использовать блокирующие сокеты во всех ситуациях (и тогда, когда нам нужно обрабатывать несколько сокетов одновременно, и тогда, когда нам требуется возможность прервать операцию, выполняемую над сокетом). Рассмотрим, тем не менее, две функции, необходимые для работы с не-блокирующими сокетами. По умолчанию функция socket() создает блокирующий сокет. Чтобы сделать его не- блокирующим, мы используем функцию fcntl(2):

Теперь любой вызов функции read() для сокета sock будет возвращать управление сразу же. Если на входе сокета нет данных для чтения, функция read() вернет значение EAGAIN. Для поверки состояния не-блокирующих сокетов можно воспользоваться функцией select(2). Функция select() способна проверять состояние нескольких дескрипторов сокетов (или файлов) сразу. Первый параметр функции – количество проверяемых дескрипторов. Второй, третий и четвертый параметры функции представляют собой наборы дескрипторов, которые следует проверять, соответственно, на готовность к чтению, записи и на наличие исключительных ситуаций. Сама функция select() – блокирующая, она возвращает управление, если хотя бы один из проверяемых сокетов готов к выполнению соответствующей операции. В качестве последнего параметра функции select() можно указать интервал времени, по прошествии которого она вернет управление в любом случае. Вызов select() для проверки наличия входящих данных на сокете sock может выглядеть так:

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

На этом мы закончим знакомство с увлекательным миром межпроцессного взаимодействия Linux. Следующая статья будет посвящена управлению процессами, сигналам и потокам.

Источник

Оцените статью