Подключенные сокеты Unix SOCK_DGRAM - PullRequest
0 голосов
/ 07 сентября 2018

Я следовал коду в этом ответе , чтобы создать пару программ, которые отправляют и получают дейтаграммы через сокет Unix.

Что неудобно в этом: На стороне, которая создает первый сокет (то есть «сервер»), я не могу использовать вызовы send, recv, read или write, потому что нет пункт назначения установлен (эти вызовы завершаются с ошибкой «Требуется адрес пункта назначения».

Я попытался обойти эту проблему, добавив начальный вызов к recvfrom и используя адрес, полученный там, но он никогда не имеет правильного значения (по крайней мере, в OSX). Также не работает sendto, так как мы не знаем адрес клиента.

То, как я понял, работает примерно следующим образом:

  1. Запустить серверную программу, которая:
    1. Вызывает socket и bind для создания сокета сервера.
    2. Здесь ждет.
  2. Запустить клиентскую программу, которая:
    1. Вызывает socket и bind для создания клиентского сокета.
    2. Он знает путь к сокету сервера и вызывает connect.
    3. Эта сторона теперь настроена правильно.
  3. Серверная программа:
    1. Принимает путь к клиентскому сокету через стандартный ввод
    2. Копирует путь к struct sockaddr_un и использует его для вызова connect (как в связанном ответе).

Это довольно неловко! Если бы я делал это с SOCK_STREAM сокетами, я мог бы использовать listen и accept; поток намного более простой, и серверу не нужно знать путь к сокету клиента.

Есть ли более элегантный способ подключения этих разъемов?

Ответы [ 3 ]

0 голосов
/ 07 сентября 2018

Один из способов решения проблемы:

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

Тогда ваш сервер сможет ответить нужному клиенту, используя sendto.

Псевдо-пример:

void handle_my_message(const my_message_t *msg)
{
   struct sockaddr_un client_address = msg->header.sender;
   my_message_response_t response_msg;  

   ... handle the message and fill the response...

   // Send response message
   sendto(fd, &response_msg, sizeof(response_msg), 0,
          (struct sockaddr*)&client_address, sizeof(client_address));   
}

Таким образомпрограммам вашего сервера не нужно вести журнал подключений.

Вместо struct sockaddr_un в заголовке вы, возможно, должны использовать что-то меньшее и более переносимое, которое можно преобразовать в struct sockaddr_un.

0 голосов
/ 07 сентября 2018

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

Пример кода для клиента (в python, потому что его легко и быстро создать прототип - его легко перевести на эквивалентный C):

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
client_addr = "/tmp/ux_client"
if os.path.exists(client_addr):
    os.remove(client_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(client_addr)
for n in range(5):
    data = "Hello " + str(n)
    data = data.encode()
    print("Sent '{}' to {}".format(data, server_addr))
    sock.sendto(data, server_addr)
    data, addr = sock.recvfrom(16000)
    print("Got '{}' back from {}".format(data, addr))

Кроме того, вы можете выполнить connect на стороне клиента. Поскольку это сокет дейтаграммы, он фактически не создает соединения между ними, но фиксирует адрес конечной точки сервера, освобождая вас от необходимости указывать адрес сервера при каждой отправке (т. Е. Вы можно использовать простой send вместо sendto).

Для полноты вот эхо-сервер, соответствующий приведенному выше:

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
if os.path.exists(server_addr):
    # Bind will fail if endpoint exists
    os.remove(server_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(server_addr)
while True:
    data, addr = sock.recvfrom(16000)
    print("Got '{}' from {}".format(data, addr))
    sock.sendto(data, addr)

EDIT

Хм ... Теперь я вижу, что вы говорите, что вы уже bind используете сокет клиента, а затем connect на стороне сервера. Но это означает, что вам просто нужно, чтобы сервер сначала использовал recvfrom один раз для получения адреса клиента. ОС будет отправлять адрес вместе, и вам не нужно использовать внеполосный механизм.

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

Обновлен сервер с начальным recvfrom, за которым следует connect:

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
if os.path.exists(server_addr):
    # Bind will fail if endpoint exists
    os.remove(server_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(server_addr)
client_addr = None
while True:
    if client_addr:
        data = sock.recv(16000)
    else:
        data, client_addr = sock.recvfrom(16000)
        sock.connect(client_addr)
    print("Got '{}' from {}".format(data, client_addr))
    sock.send(data)

Обновлен клиент с подключенным сокетом.

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
client_addr = "/tmp/ux_client"
if os.path.exists(client_addr):
    os.remove(client_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(client_addr)
sock.connect(server_addr)
for n in range(5):
    data = ("Hello " + str(n)).encode()
    print("Sent '{}'".format(data))
    sock.send(data)
    data = sock.recv(16000)
    print("Got '{}' back".format(data))
0 голосов
/ 07 сентября 2018

Сокеты SOCK_DGRAM (UDP) являются «без соединения», поэтому вы не можете «соединить» эти два сокета.Они только отправляют пакеты на указанный адрес назначения, и клиент просто перехватывает его.Поэтому вы сначала должны решить, собираетесь ли вы использовать SOCK_DGRAM (UDP) или SOCK_STREAM (TCP).

Если вы используете UDP-сокеты, сокету на стороне клиента не нужно connect, вы просто sendto адрес назначения (в данном случае сервер) после создания и привязки.

Так что, если вам нужно выделенное подключенное соединение, вам лучше использовать TCP-сокет.Или, если вы используете это через Интернет, ближайшая вещь, которую вы можете найти для UDP, это Пробивание отверстий .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...