Программирование сокетов в linux

Программирование сокетов в linux

Сокеты (англ. socket — разъём) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться как на одной ЭВМ, так и на различных ЭВМ, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Принципы сокетов¶

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

Каждый сокет имеет свой адрес. ОС семейства UNIX могут поддерживать много типов адресов, но обязательными являются INET-адрес и UNIX-адрес. Если привязать сокет к UNIX-адресу, то будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём чтения/записи из него (см. Доменный сокет Unix). Сокеты типа INET доступны из сети и требуют выделения номера порта.

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

Основные функции¶

Общие
Socket Создать новый сокет и вернуть файловый дескриптор
Send Отправить данные по сети
Receive Получить данные из сети
Close Закрыть соединение
Серверные
Bind Связать сокет с IP-адресом и портом
Listen Объявить о желании принимать соединения. Слушает порт и ждет когда будет установлено соединение
Accept Принять запрос на установку соединения
Клиентские
Connect Установить соединение

socket()¶

Создаёт конечную точку соединения и возвращает файловый дескриптор. Принимает три аргумента:

domain указывающий семейство протоколов создаваемого сокета

  • AF_INET для сетевого протокола IPv4
  • AF_INET6 для IPv6
  • AF_UNIX для локальных сокетов (используя файл)

type

  • SOCK_STREAM (надёжная потокоориентированная служба (сервис) или потоковый сокет)
  • SOCK_DGRAM (служба датаграмм или датаграммный сокет)
  • SOCK_RAW (Сырой сокет — сырой протокол поверх сетевого уровня).

protocol

Протоколы обозначаются символьными константами с префиксом IPPROTO_* (например, IPPROTO_TCP или IPPROTO_UDP). Допускается значение protocol=0 (протокол не указан), в этом случае используется значение по умолчанию для данного вида соединений.

Функция возвращает −1 в случае ошибки. Иначе, она возвращает целое число, представляющее присвоенный дескриптор.

Пример на Python

Связывает сокет с конкретным адресом. Когда сокет создается при помощи socket(), он ассоциируется с некоторым семейством адресов, но не с конкретным адресом. До того как сокет сможет принять входящие соединения, он должен быть связан с адресом. bind() принимает три аргумента:

  1. sockfd — дескриптор, представляющий сокет при привязке
  2. serv_addr — указатель на структуру sockaddr, представляющую адрес, к которому привязываем.
  3. addrlen — поле socklen_t, представляющее длину структуры sockaddr.

Возвращает 0 при успехе и −1 при возникновении ошибки.

Пример на Python

Автоматическое получение имени хоста.

listen()¶

Подготавливает привязываемый сокет к принятию входящих соединений. Данная функция применима только к типам сокетов SOCK_STREAM и SOCK_SEQPACKET. Принимает два аргумента:

  1. sockfd — корректный дескриптор сокета.
  2. backlog — целое число, означающее число установленных соединений, которые могут быть обработаны в любой момент времени. Операционная система обычно ставит его равным максимальному значению.

После принятия соединения оно выводится из очереди. В случае успеха возвращается 0, в случае возникновения ошибки возвращается −1.

Пример на Python

accept()¶

Используется для принятия запроса на установление соединения от удаленного хоста. Принимает следующие аргументы:

  1. sockfd — дескриптор слушающего сокета на принятие соединения.
  2. cliaddr — указатель на структуру sockaddr, для принятия информации об адресе клиента.
  3. addrlen — указатель на socklen_t, определяющее размер структуры, содержащей клиентский адрес и переданной в accept(). Когда accept() возвращает некоторое значение, socklen_t указывает сколько байт структуры cliaddr использовано в данный момент.

Функция возвращает дескриптор сокета, связанный с принятым соединением, или −1 в случае возникновения ошибки.

Пример на Python

connect()¶

Устанавливает соединение с сервером.

Некоторые типы сокетов работают без установления соединения, это в основном касается UDP-сокетов. Для них соединение приобретает особое значение: цель по умолчанию для посылки и получения данных присваивается переданному адресу, позволяя использовать такие функции как send() и recv() на сокетах без установления соединения.

Загруженный сервер может отвергнуть попытку соединения, поэтому в некоторых видах программ необходимо предусмотреть повторные попытки соединения.

Возвращает целое число, представляющее код ошибки: 0 означает успешное выполнение, а −1 свидетельствует об ошибке.

Пример на Python

Передача данных¶

Для передачи данных можно пользоваться стандартными функциями чтения/записи файлов read и write, но есть специальные функции для передачи данных через сокеты:

Нужно обратить внимание, что при использовании протокола TCP (сокеты типа SOCK_STREAM) есть вероятность получить меньше данных, чем было передано, так как ещё не все данные были переданы, поэтому нужно либо дождаться, когда функция recv возвратит 0 байт, либо выставить флаг MSG_WAITALL для функции recv, что заставит её дождаться окончания передачи. Для остальных типов сокетов флаг MSG_WAITALL ничего не меняет (например, в UDP весь пакет = целое сообщение).

send, sendto — отправка данных.

Сокет´ (socket) — название программного интерфейса для обеспечения обмена данными между процессами. Процессы при таком обмене могут исполняться на одном компьютере или на различных компьютерах, связанных между собой сетью. Сокет — абстрактный объект, представляющий конечную точку соединения.

Интерфейс сокетов впервые появился в BSD Unix. Программный интерфейс сокетов описан в стандарте POSIX и в той или иной мере поддерживается всеми современными операционными системами.

Читайте также:  Управление tv box с телефона

5.1. Использование классических блокирующих сокетов

5.1.1. Алгоритм работы сервера и клиента

С установлением соединения. Порядок использования блокирующих сокетов с установлением соединения:

На стороне сервера:

1. Инициализировать библиотеку Winsock ( WSAStartup , только для платформы Windows).

2. Создать сокет ( socket ).

3. Связать сокет ( bind ).

4. Перевести его в слушающий режим ( listen ).

5. Принять запрос на соединение ( accept ).

6. Получить ( recv ) или отправить ( send ) данные.

7. Отключить сокет ( shutdown ).

8. Закрыть сокет ( close , для Windows — closesocket ).

9. Закрыть библиотеку Winsock ( WSACleanup , только для платформы Windows).

На стороне клиента:

1. Инициализировать библиотеку Winsock ( WSAStartup , только для платформы Windows)

2. Создать сокет ( socket ).

3. Послать запрос на соединение ( connect ).

4. Отправить ( send ) или получить ( recv ) данные.

5. Отключить сокет ( shutdown ).

6. Закрыть сокет ( close , для Windows — closesocket ).

7. Закрыть библиотеку Winsock ( WSACleanup , только для платформы Windows).

Без установления соединения. Порядок использования блокирующих сокетов без установления соединения (одинаково для сервера и клиента):

1. Инициализировать библиотеку Winsock ( WSAStartup , только для платформы Windows).

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

2. Создать сокет ( socket ).

3. Связать сокет ( bind ).

4. Получить ( recvfrom ) или отправить ( sendto ) данные.

5. Закрыть сокет ( close , для Windows — closesocket ).

6. Закрыть библиотеку Winsock ( WSACleanup , только для платформы Windows).

5.1.2. Функции API сокетов

Для использования сокетов в системе Windows необходимо подключать заголовочный файл , в Linux — . Если приложение разрабатывается в системе Microsoft Visual C++, то к проекту надо подключить библиотеку ws2_32.lib . Для этого надо выполнить команду главного меню í Project í Settings Link Category: s General

и дописать имя библиотечного файла в поле « Object/library modules » (рис. 5.1 ).

Рис. 5.1. Подключение библиотеки ws2_32.lib в Microsoft Visual C++

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

Создание серверного или клиентского сокета. Дескриптор (описатель) сокета — это переменная типа SOCKET . Сокет создаётся с помощью функции

SOCKET socket(int domain, int type, int protocol);

Возвращаемое значение: дескриптор сокета в случае успеха; −1 (Linux) или INVALID_SOCKET (Windows) — ошибка. Входной параметр domain — константа, указывающая, какой домен нужен сокету. Обычно используются домены AF_INET (т. е. Internet) и AF_LOCAL (применяется для межпроцессного взаимодействия (IPC) на одной и той же машине).

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

С помощью параметра type задаётся тип создаваемого сокета. Чаще встречаются следующие значения:

∙ SOCK_STREAM — обеспечивают надёжный дуплексный протокол на основе установления логического соединения. Если говорится о семействе протоколов TCP/IP, то это TCP;

∙ SOCK_DGRAM — обеспечивают ненадежный сервис доставки датаграмм. В рамках TCP/IP это протокол UDP;

∙ SOCK_RAW — предоставляют доступ к некоторым датаграммам на уровне протокола IP. Они используются в особых случаях, например для просмотра всех ICMP-сообщений.

Параметр protocol показывает, какой протокол следует использовать с данным сокетом. В контексте TCP/IP он обычно неявно определяется типом сокета, поэтому в качестве значения задают 0 . Иногда, например, в случае rawсокетов, имеется несколько возможных протоколов, тогда данный параметр необходимо задавать явно.

Система не освобождает ресурсы, выделенные при вызове socket() , пока не произойдет вызова closesocket() . Каждый вызов socket() должен иметь соответствующий вызов closesocket() во всех возможных путях исполнения. Результатом выполнения системного вызова closesocket() является только обращение к интерфейсу для закрытия сокета, а не закрытие самого сокета. Другими словаи, это всего лишь команда для операционной системы закрыть сокет, а само освобождение ресурсов будет выполнено системой позже, в соответствии с алгоритмами её работы.

Подключение клиента к серверу. Соединение с удалённым сокетом устанавливается с помощью функции

int connect(SOCKET s, struct sockaddr *peer, int peer_len);

Возвращаемое значение: 0 — нормально; −1 (Linux) или не 0 (Windows) — ошибка. Параметр s — дескриптор сокета, который получен в результате вызова функции socket() . Параметр peer указывает на структуру, в которой хранится адрес удаленного хоста и некоторая дополнительная информация. Для домена AF_INET это структура типа sockaddr_in . Параметр peer_len содержит размер структуры (в байтах), на которую указывает peer .

После установления соединения можно передавать данные.

Передача и приём данных. В системе Linux передача и приём информации выполняется с помощью функций read() и write() , которым передаётся дескриптор сокета. В системе Windows используются функции recv() (приём) и send() (передача данных):

int recv(SOCKET s, void *buf,

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

size_t len, int flags); int send(SOCKET s, const void *buf,

size_t len, int flags);

Возвращаемое значение: число принятых или переданных байтов в случае успеха; −1 в случае ошибки.

Параметры buf и len — адрес буфера для приёма или отправки информации и его длина. Значение параметра flags зависит от системы, но и Linux,

и Windows поддерживают следующие флаги:

∙ MSG_OOB — следует послать или принять срочные данные;

∙ MSG_PEEK — используется для просмотра поступивших данных без их удаления из приёмного буфера. После возврата из системного вызова данные еще могут быть получены при последующем вызове read()

Читайте также:  Сброс до заводских настроек android через рекавери

∙ MSG_DONTROUTE — сообщает ядру, что не надо выполнять обычный алгоритм маршрутизации. Как правило, используется программами маршрутизации или для диагностических целей.

При работе с UDP нужны еще системные вызовы recvfrom() и sendto() . Они очень похожи на recv() и send() , но позволяют при отправке датаграммы задать адрес назначения, а при приёме — получить адрес источника:

int recvfrom(SOCKET s, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);

int sendto(SOCKET s, const void *buf, size_t len, int flags, const struct sockaddr *to, int tolen);

Возвращаемое значение: число принятых или переданных байтов в случае успеха; −1 при ошибке.

Первые четыре параметра: s , buf , len и flags — такие же, как в вызовах recv() и send() . Параметр from в вызове recvfrom() указывает на структуру, в которую ядро помещает адрес источника пришедшей датаграммы. Длина этого адреса хранится в целом числе, на которое указывает параметр fromlen . Обратите внимание, что fromlen — это указатель на целое. Аналогично параметр to в вызове sendto() указывает на адрес структуры, содержащей адреса назначения датаграммы, а параметр tolen — длина этого адреса. Заметьте, что to — это целое, а не указатель.

Функции send() и recv() не являются атомарными и нет никакой rарантии, что затребованные данные будут действительно отправлены или получены в результате одного вызова этих функций. Приходится проверять возвращаемое значение (фактическое количество принятых или отправленных байт)

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

и повторно принимать или отправлять данные до тех пор, пока все они не будут приняты или переданы.

Связывание серверного сокета. На стороне сервера необходимо привязать адрес интерфейса и номер порта к прослушивающему сокету. Для этого предназначен вызов bind() :

int bind(SOCKET s, const struct sockaddr *name, int namelen);

Возвращаемое значение: 0 — нормально, −1 (Linux) или SOCKET_ERROR (Windows) — ошибка. Параметр s — дескриптор слушающего сокета. С помощью параметров name и namelen передаются порт и сетевой интерфейс, которые нужно прослушивать. Обычно в качестве адреса задается константа INADDR_ANY . Это означает, что будет принято соединение, запрашиваемое по любому интерфейсу. Если хосту с несколькими сетевыми адресами нужно принимать соединения только по одному интерфейсу, то следует указать IP-адрес этого интерфейса. Параметр namelen — длина структуры sockaddr_in .

Перевод серверного сокета в слушающим режим. После привязки локального адреса к сокету нужно перевести сокет в режим прослушивания входящих соединений с помощью системного вызова listen() . Его единственная задача — пометить сокет как прослушивающий. Когда хосту поступает запрос на установление соединения, ядро ищет в списке прослушивающих сокетов тот, для которого адрес назначения и номер порта соответствуют указанным в запросе.

int listen(SOCKET s, int backlog);

Возвращаемое значение: 0 — нормально, −1 (Linux) или SOCKET_ERROR (Windows) — ошибка. Параметр s — дескриптор сокета, который нужно перевести в режим прослушивания. Параметр backlog — максимальное число ожидающих, но еще не принятых соединений. Следует отметить, что это не максимальное число одновременных соединений с данным портом, а лишь максимальное число частично установленных соединений, ожидающих в очереди, пока приложение их примет (описание системного вызова accept() дано ниже). Традиционно значение параметра backlog() — не более пяти соединений, но в современных реализациях, которые должны поддерживать приложения с высокой нагрузкой, например, Web-сервера, оно может быть намного больше. Поэтому, чтобы выяснить его истинное значение, необходимо изучить документацию по конкретной системе. Если задать значение, большее максимально допустимого, то система уменьшит его, не сообщив об ошибке.

Слушающие соединения (listening connections) — это пассивные серверные сокеты, ожидающие клиента. Как только клиент делает новый запрос, сервер

Ю.В. Земсков. Вычислительные сети. Версия 0.20. — Санкт-Петербургский гос. университет гражданской авиации , 2012

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

Приём сервером запроса от клиента на установку соединения. И последний вызов, который будет здесь рассмотрен, — это accept() . Он служит для приёма соединения, ожидающего во входной очереди. После того как соединение принято, его можно использовать для передачи данных, например, с помощью вызовов recv() и send() . В случае успеха accept() возвращает дескриптор нового сокета, по которому и будет происходить обмен данными. Номер локального порта для этого сокета такой же, как и для прослушивающего сокета. Адрес интерфейса, на который поступил запрос о соединении, называется локальным. Адрес и номер порта клиента считаются удаленными.

Обратите внимание, что оба сокета имеют один и тот же номер локального порта. Это нормально, поскольку TCP-соединение полностью определяется четырьмя параметрами: локальным адресом, локальным портом, удалённым адресом и удалённым портом. Поскольку удалённые адрес и порт для этих двух сокетов различны, то ядро может отличить их друг от друга.

int accept(SOCKET s, struct sockaddr *addr, int *addrlen);

Функция accept() блокируется до тех пор, пока от клиента не поступит запрос соединения, после чего она возвращает новый сокет. Возвращаемое значение: дескриптор этого нового сокета в случае успеха; −1 (Linux) или INVALID_SOCKET (Windows) — ошибка.

Параметр s — дескриптор слушающего сокета. Вызов accept() возвращает адрес подключившегося к серверу клиента в структуре sockaddr_in , на которую указывает параметр addr . Целому числу, на которое указывает параметр addrlen , ядро присваивает значение, равное длине этой структуры. Часто нет необходимости знать адрес клиента, тогда в качестве addr и addrlen передаётся NULL .

Читайте также:  Msi motherboard intel platform

5.1.3. Пример использования блокирующих TCP-сокетов в однопоточном сервере и клиенте

Рассмотрим пример сервера и клиента Winsock (листинги 2 и 3 ), написанные на языке C (консольные приложения). Здесь клиент соединяется с сервером и посылает ему сообщение, состоящее из пяти символов. Сервер принимает сообщение и выводит его на экран. Сервер является однопоточным приложением, т. е. может одновременно работать только с одним клиентом.

27.3. Программирование сокетов

27.3.1. Что такое сокет?

Сокет — это двунаправленный канал между двумя компьютерами в сети, который обеспечивает конечную точку соединения. «Двунаправленный» означает, что данный могут передаваться в двух направлениях — от клиента к серверу и наоборот. Понятие сокета — абстрактное, это как бы программный соединитель, через который обмениваются данными программа-сервер и программа-клиент.

Сокет-интерфейс используется для получения доступа к транспортному уровню протокола TCP/IP и представляет собой набор системных вызовов операционной системы и библиотечных функций на языке С. Все эти функции можно условно разделить на три группы:

? функции установления связи;

? функции сетевого ввода/вывода.

Общий алгоритм работы сетевой программы, использующей сокеты:

1. Подготовить (создать) сокет — функция socket().

2. Связать сокет — функция bind().

3. Установить связь с удаленным компьютером (клиенту — установить связь, а серверу — ожидать установления связи).

4. Произвести обмен данными — функции recv() и send().

5. Завершить сеанс связи — close() и shutdown().

Библиотечные функции для работы с сокетами находятся в заголовочном файле sys/socket.h, поэтому для любой сетевой программы обязательна следующая директива:

Данный текст является ознакомительным фрагментом.

Читать книгу целиком

Похожие главы из других книг:

ГЛАВА 12 Сетевое программирование с помощью сокетов Windows

ГЛАВА 12 Сетевое программирование с помощью сокетов Windows Именованные каналы пригодны для организации межпроцессного взаимодействия как в случае процессов, выполняющихся на одной и той же системе, так и в случае процессов, выполняющихся на компьютерах, связанных друг с

17.7. Ошибки сокетов

17.7. Ошибки сокетов Некоторые значения errno встречаются только при работе с сокетами. Ниже приведен список специфических ошибок сокетов вместе с краткими их описаниями. EADDRINUSE Запрашиваемый адрес уже используется и не может быть переприсвоен. EADDRNOTAVAIL Запрашивается

Программный интерфейс сокетов

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

Программный интерфейс сокетов

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

Пара сокетов

Пара сокетов Пара сокетов (socket pair) для соединения TCP — это кортеж (группа взаимосвязанных элементов данных или записей) из четырех элементов, определяющий две конечных точки соединения: локальный IP-адрес, локальный порт TCP, удаленный IP-адрес и удаленный порт TCP. В SCRIPT

3.2. Структуры адреса сокетов

3.2. Структуры адреса сокетов Большинство функций сокетов используют в качестве аргумента указатель на структуру адреса сокета. Каждый набор протоколов определяет свою собственную структуру адреса сокетов. Имена этих структур начинаются с sockaddr_ и заканчиваются

Глава 7 Параметры сокетов

Глава 7 Параметры сокетов 7.1. Введение Существуют различные способы получения и установки параметров сокетов:? функции getsockopt и setsockopt;? функция fcntl;? функция ioctl.Эту главу мы начнем с описания функций getsockopt и setsockopt. Далее мы приведем пример, в котором выводятся заданные по

7.4. Состояния сокетов

7.4. Состояния сокетов Для некоторых параметров сокетов время их установки или получения зависит некоторым образом от состояния сокета. Далее мы обсудим эту зависимость для тех параметров, к которым это относится.Следующие параметры сокетов наследуются присоединенным

7.6. Параметры сокетов IPv4

7.6. Параметры сокетов IPv4 Эти параметры сокетов обрабатываются IPv4 и для них аргумент level равен IPPROTO_IP. Обсуждение пяти параметров сокетов многоадресной передачи мы отложим до раздела

7.8. Параметры сокетов IPv6

7.8. Параметры сокетов IPv6 Эти параметры сокетов обрабатываются IPv6 и имеют аргумент level, равный IPPROTO_IPV6. Мы отложим обсуждение пяти параметров сокетов многоадресной передачи до раздела 21.6. Отметим, что многие из этих параметров используют вспомогательные данные с функцией

7.9. Параметры сокетов TCP

7.9. Параметры сокетов TCP Для сокетов TCP предусмотрены два специальных параметра. Для них необходимо указывать level

7.10. Параметры сокетов SCTP

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

15.4. Функции сокетов

15.4. Функции сокетов Функции сокетов применяются к доменным сокетам Unix с учетом некоторых особенностей и ограничений. Далее мы перечисляем требования POSIX, указывая, где они применимы. Отметим, что на сегодняшний день не все реализации соответствуют этим

5.5.1. Концепции сокетов

5.5.1. Концепции сокетов При создании сокета необходимо задать три параметра, тип взаимодействия, пространство имен и протокол.Тип взаимодействия определяет способ интерпретации передаваемых данных и число абонентов. Данные, посылаемые через сокет, формируются в блоки,

5.5.7. Пары сокетов

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

Ссылка на основную публикацию
Программа для отформатировать флешку
Процесс форматирования флешки мало отличается от форматирования HDD или SSD-дисков. Далее мы рассмотрим лучшие программы для форматирования флешек (такие как...
Приложение следить за человеком по номеру телефона
Отслеживание по номеру телефона - это приложение для Android, благодаря которому вы всегда будете знать где находятся ваши родные и...
Приложение чтобы играть андроид игры на компьютер
Самый мощный эмулятор Android из всех Newest ReleaseВерсия 7.1.3 2020.03.04 Играйте бесплатно в любые игры для Android. Наслаждайтесь оптимизированной графикой...
Программа для оцифровки винила
Каталог продаваемых пластинок (49230) Минимальные аппаратные требования, или что надо иметь для оцифровки Компьютер со звуковой картой. Проигрыватель винила Корректор...
Adblock detector