Подтвердить что ты не робот

Unix Domain Socket: использование обмена датаграммой между одним серверным процессом и несколькими клиентскими процессами

Я хотел бы установить соединение IPC между несколькими процессами в Linux. Я никогда раньше не использовал сокеты UNIX, и поэтому я не знаю, правильно ли это относится к этой проблеме.

Один процесс получает данные (неформатированные, двоичные) и должен распространять эти данные через локальный сокет AF_UNIX, используя протокол датаграммы (то есть аналогичный UDP с AF_INET). Данные, отправленные из этого процесса в локальный сокет Unix, должны приниматься несколькими клиентами, которые прослушивают один и тот же сокет. Количество приемников может меняться.

Для этого используется следующий код для создания сокета и отправки ему данных (серверный процесс):

struct sockaddr_un ipcFile;
memset(&ipcFile, 0, sizeof(ipcFile));
ipcFile.sun_family = AF_UNIX;
strcpy(ipcFile.sun_path, filename.c_str());

int socket = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile));
...
// buf contains the data, buflen contains the number of bytes
int bytes = write(socket, buf, buflen);
...
close(socket);
unlink(ipcFile.sun_path);

Эта запись возвращает -1 с сообщением errno ENOTCONN ( "Конечная точка транспорта не подключена" ). Я предполагаю, что это происходит потому, что в настоящее время ни один процесс приема не прослушивает этот локальный сокет, правильно?

Затем я попытался создать клиента, который подключается к этому сокету.

struct sockaddr_un ipcFile;
memset(&ipcFile, 0, sizeof(ipcFile));
ipcFile.sun_family = AF_UNIX;
strcpy(ipcFile.sun_path, filename.c_str());

int socket = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile));
...
char buf[1024];
int bytes = read(socket, buf, sizeof(buf));
...
close(socket);

Здесь сбой не выполняется ( "Адрес уже используется" ). Итак, нужно ли устанавливать некоторые параметры сокетов, или это вообще неправильный подход?

Заранее благодарим за любые комментарии/решения!

4b9b3361

Ответ 1

Там есть трюк для использования сокетов UNIX datagram. В отличие от потоковых сокетов (домен tcp или unix), сокетам дейтаграмм нужны конечные точки, определенные как для сервера, так и для клиента. Когда устанавливается соединение в потоковых сокетах, конечная точка для клиента неявно создается операционной системой. Независимо от того, соответствует ли это эфемерному порту TCP/UDP или временному inode для домена unix, конечная точка для клиента создается для вас. Вот почему вам обычно не нужно вызывать вызов bind() для сокетов потока в клиенте.

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

С сокетами дейтаграмм, в частности сокетами UNIX домена дейтаграммы, клиент должен bind() к своей конечной точке, а затем connect() к конечной точке сервера. Вот ваш код клиента, слегка измененный, с некоторыми другими лакомствами, брошенными в:

char * server_filename = "/tmp/socket-server";
char * client_filename = "/tmp/socket-client";

struct sockaddr_un server_addr;
struct sockaddr_un client_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, server_filename, 104); // XXX: should be limited to about 104 characters, system dependent

memset(&client_addr, 0, sizeof(client_addr));
client_addr.sun_family = AF_UNIX;
strncpy(client_addr.sun_path, client_filename, 104);

// get socket
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);

// bind client to client_filename
bind(sockfd, (struct sockaddr *) &client_addr, sizeof(client_addr));

// connect client to server_filename
connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr));

...
char buf[1024];
int bytes = read(sockfd, buf, sizeof(buf));
...
close(sockfd);

В этот момент ваш сокет должен быть полностью настроен. Я думаю, что теоретически вы можете использовать read()/write(), но обычно я использую send()/recv() для сокетов датаграмм.

Обычно вам нужно проверить ошибку после каждого из этих вызовов и затем выпустить perror(). Это очень поможет вам, когда все пойдет не так. В общем, используйте шаблон, подобный этому:

if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
    perror("socket failed");
}

Это касается практически любых системных вызовов C.

Лучшей ссылкой для этого является Стивен "Сетевое программирование Unix". В третьем издании, раздел 15.4, на страницах 415-419 приведены некоторые примеры и перечислены многие из предостережений.

Кстати, ссылаясь на

Я предполагаю, что это происходит потому, что в настоящий момент ни один процесс приема не прослушивает этот локальный сокет, правильно?

Я думаю, что вы правы в ошибке ENOTCONN от write() на сервере. Разъем UDP обычно не будет жаловаться, потому что у него нет возможности узнать, прослушивает ли клиентский процесс. Однако сокеты datagram домена unix различны. Фактически, write() фактически блокирует, если буфер приема клиента заполнен, а не удаляет пакет. Это делает сокеты datagram домена unix намного превосходящими UDP для IPC, потому что UDP, безусловно, будет отбрасывать пакеты при загрузке, даже на localhost. С другой стороны, это означает, что вы должны быть осторожны с быстрыми писателями и медленными читателями.

Ответ 2

Ближайшей причиной вашей ошибки является то, что write() не знает, куда вы хотите отправить данные. bind() устанавливает имя вашей стороны сокета - то есть. откуда поступают данные. Чтобы установить целевую сторону сокета, вы можете использовать connect(); или вы можете использовать sendto() вместо write().

Другая ошибка ( "Адрес уже используется" ) заключается в том, что только один процесс может bind() на адрес.

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

В качестве альтернативы вы можете использовать локальную многоадресную IP-сеть вместо сокетов домена UNIX (сокеты домена UNIX не поддерживают многоадресную рассылку).

Ответ 3

Если речь идет о вещании (как я понимаю), то согласно unix (4) - семейство протоколов UNIX, вещание недоступно в UNIX-сокетах UNIX:

Семейство протоколов Unix Ns -domain не поддерживает широковещательная адресация или любая форма подстановочного соответствия по входящим сообщениям. Все адреса являются абсолютными или относительные пути других сокетов Unix Ns-domain.

Может быть, многоадресная рассылка может быть вариантом, но я чувствую, что она недоступна с POSIX, хотя Linux поддерживает многоадресную рассылку доменов UNIX.

Также см.: Представление многоадресных сокетов Unix.

Ответ 4

Не было бы проще использовать разделяемую память или именованные каналы? Сокет - это соединение между двумя процессами (на той же или другой машине). Это не метод массовой коммуникации.

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

Ответ 5

Это произойдет из-за сервер или клиент умирают перед отключением/удалением для привязки файла bind(). любой клиент/сервер, использующий этот путь привязки, попробуйте снова запустить сервер.

решения: когда вы хотите снова связать, просто проверьте, что файл уже ассоциирован, а затем отсоедините этот файл. Как сделать шаг: сначала проверить доступ к этому файлу путем доступа (2); если да, то отсоедините (2) его. поставьте этот мир кода перед вызовом bind(), позиция независима.

 if(!access(filename.c_str()))
    unlink(filename.c_str());

для дополнительной справки read unix (7)

Ответ 6

Вы должны заглянуть в многоадресную рассылку IP вместо домена Unix. В настоящее время вы просто пытаетесь писать в никуда. И если вы подключаетесь к одному клиенту, вы будете писать только этому клиенту.

Этот материал не работает так, как вы думаете.

Ответ 7

Вы можете решить ошибку привязки с помощью следующего кода:

int use = yesno;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&use, sizeof(int));

С протоколом UDP вы должны вызывать connect(), если вы хотите использовать write() или send(), иначе вы должны использовать sendto().

Для достижения ваших требований может понадобиться следующий псевдо-код:

sockfd = socket(AF_INET, SOCK_DGRAM, 0)
set RESUSEADDR with setsockopt
bind()
while (1) {
   recvfrom()
   sendto()
}